From a338eda462c766348dfe05667ae11c489373ab08 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Tue, 8 Jul 2025 23:58:43 +0900 Subject: [PATCH 01/79] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=A1=B0=ED=9A=8C=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../region/dto/RegionRegistReqDTO.java | 6 + .../service/ScheduleCommandService.java | 38 ++++++ .../controller/ScheduleController.java | 109 +++++++++++++++++- 3 files changed, 151 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java b/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java index f31eedc..73c5311 100644 --- a/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java +++ b/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java @@ -16,6 +16,12 @@ public class RegionRegistReqDTO { @Schema(description = "지역명 소분류", example = "강남구") public String regionNm2; + @Schema(description = "x 좌표", example = "127.04892851392") + public String x; + + @Schema(description = "y 좌표", example = "37.5091105328378") + public String y; + // public int regionNum; // 지역 번호 // public String regionLevel1; // 대분류 // public String regionLevel2; // 시/군 diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 77b867c..bb5e94f 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -1,7 +1,45 @@ package com.barogagi.schedule.command.service; +import com.barogagi.approval.mapper.ApprovalMapper; +import com.barogagi.kakaoplace.client.KakaoPlaceClient; +import com.barogagi.plan.dto.PlanRegistReqDTO; +import com.barogagi.plan.query.mapper.CategoryMapper; +import com.barogagi.schedule.dto.KakaoPlaceReqDTO; +import com.barogagi.schedule.dto.ScheduleRegistReqDTO; +import com.barogagi.schedule.query.service.ScheduleQueryService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.List; + @Service public class ScheduleCommandService { +// private final CategoryMapper categoryMapper; +// private final KakaoPlaceClient kakaoPlaceClient; + +// @Autowired +// public ScheduleCommandService(CategoryMapper categoryMapper, +// KakaoPlaceClient kakaoPlaceClient){ +// this.categoryMapper = categoryMapper; +// this.kakaoPlaceClient = kakaoPlaceClient; +// } + + public void registSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { + +// List planRegistReqDTOList = scheduleRegistReqDTO.getPlanRegistReqDTOList(); +// KakaoPlaceReqDTO kakaoPlaceReqDTO; +// +// // NaverBlogReqDTO naverBlogReqDTO; +// +// int radious = scheduleRegistReqDTO.getRadius(); +// int resultSize = 10; +// +// for (PlanRegistReqDTO plan : planRegistReqDTOList) { +// String queryString = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); // TODO. 검색 쿼리 고려 필요 +// String x = plan.getRegionRegistReqDTO().getX(); +// String y = plan.getRegionRegistReqDTO().getY(); +// +// kakaoPlaceClient.searchKakaoPlace(queryString, x, y, radious); +// } + } } diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 6dbcb99..12d2e59 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -6,12 +6,16 @@ import com.barogagi.plan.query.service.PlanQueryService; import com.barogagi.plan.query.vo.PlanDetailVO; import com.barogagi.response.ApiResponse; +import com.barogagi.schedule.command.service.ScheduleCommandService; import com.barogagi.schedule.dto.ScheduleDetailResDTO; +import com.barogagi.schedule.dto.ScheduleRegistReqDTO; import com.barogagi.schedule.query.service.ScheduleQueryService; import com.barogagi.schedule.query.vo.ScheduleDetailVO; import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.core.env.Environment; @@ -30,6 +34,7 @@ public class ScheduleController { private final InputValidate inputValidate; private final ScheduleQueryService scheduleQueryService; + private final ScheduleCommandService scheduleCommandService; private final PlanQueryService planQueryService; private final String API_SECRET_KEY; @@ -37,10 +42,12 @@ public class ScheduleController { public ScheduleController(Environment environment, InputValidate inputValidate, ScheduleQueryService scheduleQueryService, + ScheduleCommandService scheduleCommandService, PlanQueryService planQueryService) { this.API_SECRET_KEY = environment.getProperty("api.secret-key"); this.inputValidate = inputValidate; this.scheduleQueryService = scheduleQueryService; + this.scheduleCommandService = scheduleCommandService; this.planQueryService = planQueryService; } @@ -95,8 +102,106 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 } finally { apiResponse.setResultCode(resultCode); apiResponse.setMessage(message); - logger.info("#$# 22 resultCode={}", resultCode); - logger.info("#$# 22 apiResponse.getResultCode()={}", apiResponse.getResultCode()); + } + return apiResponse; + } + + + @Operation(summary = "일정 등록 기능", description = "일정을 등록하는 기능입니다.") + @PostMapping("") + public ApiResponse registSchedule( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "일정 등록 요청", + required = true, + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "일정 등록 요청 예시", + value = "{\n" + + " \"scheduleNm\": \"서울 맛집 투어 일정\",\n" + + " \"startDate\": \"2025-07-01\",\n" + + " \"endDate\": \"2025-07-01\",\n" + + " \"radius\": 3,\n" + + " \"planDetailVOList\": [\n" + + " {\n" + + " \"startTime\": \"08:00\",\n" + + " \"endTime\": \"09:00\",\n" + + " \"itemNum\": 1,\n" + + " \"categoryNum\": 1,\n" + + " \"comment\": \"한식맛집\",\n" + + " \"regionRegistReqDTOList\": [\n" + + " {\n" + + " \"regionNum\": 1,\n" + + " \"regionLevel1\": \"서울특별시\",\n" + + " \"regionLevel2\": \"강남구\",\n" + + " \"x\": \"127.04892851392\",\n" + + " \"y\": \"37.5091105328378\"\n" + + " },\n" + + " {\n" + + " \"regionNum\": 2,\n" + + " \"regionLevel1\": \"서울특별시\",\n" + + " \"regionLevel2\": \"송파구\",\n" + + " \"x\": \"127.09811980036908\",\n" + + " \"y\": \"37.51113059993883\"\n" + + " }\n" + + " ],\n" + + " \"tagRegistReqDTOList\": [\n" + + " { \"tagNum\": 1, \"tagNm\": \"디저트\" },\n" + + " { \"tagNum\": 2, \"tagNm\": \"야경\" }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}" + ) + ) + ) + @RequestBody ScheduleRegistReqDTO scheduleRegistReqDTO) { + + logger.info("CALL /schedule"); + logger.info("[input] scheduleRegistReqDTO={}", scheduleRegistReqDTO.toString()); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ + if (true) { + boolean isVaildReq = true; // TODO. DTO 검증 로직 추가 필요 + if (isVaildReq) { + resultCode = "101"; + message = "입력값이 올바르지 않습니다."; + } else { + scheduleCommandService.registSchedule(scheduleRegistReqDTO); + // ScheduleDetailResDTO result = scheduleQueryService.getScheduleDetail(scheduleNum); + // logger.info("result={}", result.toString()); + + /*if (result == null) { + resultCode = "300"; + message = "조회할 일정이 존재하지 않습니다."; // TODO. 에러 메시지 정의하기 + + } else { + resultCode = "200"; + message = "일정 상세 조회 성공"; + apiResponse.setData(result); + logger.info("#$# result={}", result.toString()); + }*/ + } + + } else { + resultCode = "100"; + message = "잘못된 접근입니다."; + } + logger.info("#$# 11 resultCode={}", resultCode); + } catch (Exception e) { + resultCode = "400"; + message = "오류가 발생하였습니다."; + throw new RuntimeException(e); + + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); } return apiResponse; } From f85c2e9b80a280482706d6cc65f8385c0f201d7a Mon Sep 17 00:00:00 2001 From: RohDamin Date: Tue, 8 Jul 2025 23:59:42 +0900 Subject: [PATCH 02/79] =?UTF-8?q?fix:=20import=20=EC=9E=84=EC=8B=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=84=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/command/service/ScheduleCommandService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index bb5e94f..4b4e3c6 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -1,10 +1,10 @@ package com.barogagi.schedule.command.service; import com.barogagi.approval.mapper.ApprovalMapper; -import com.barogagi.kakaoplace.client.KakaoPlaceClient; -import com.barogagi.plan.dto.PlanRegistReqDTO; -import com.barogagi.plan.query.mapper.CategoryMapper; -import com.barogagi.schedule.dto.KakaoPlaceReqDTO; +//import com.barogagi.kakaoplace.client.KakaoPlaceClient; +//import com.barogagi.plan.dto.PlanRegistReqDTO; +//import com.barogagi.plan.query.mapper.CategoryMapper; +//import com.barogagi.schedule.dto.KakaoPlaceReqDTO; import com.barogagi.schedule.dto.ScheduleRegistReqDTO; import com.barogagi.schedule.query.service.ScheduleQueryService; import org.springframework.beans.factory.annotation.Autowired; From 638ec23c60b4cdbe51207d6833509ebeafc1b6a7 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sat, 12 Jul 2025 09:30:40 +0900 Subject: [PATCH 03/79] =?UTF-8?q?feat:=20=EC=9D=BC=EC=A0=95=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=EC=8B=9C=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20API=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kakaoplace/client/KakaoPlaceClient.java | 58 ++++++++++++ .../kakaoplace/dto/KakaoPlaceResDTO.java | 44 +++++++++ .../dto/KakaoPlaceSearchResDTO.java | 18 ++++ .../barogagi/plan/dto/PlanRegistReqDTO.java | 13 +-- .../plan/query/mapper/CategoryMapper.java | 13 +++ .../service/ScheduleCommandService.java | 93 +++++++++++++------ .../controller/ScheduleController.java | 87 +++++++++++++---- .../schedule/dto/KakaoPlaceReqDTO.java | 11 +++ .../schedule/dto/ScheduleRegistReqDTO.java | 3 +- src/main/resources/mapper/CategoryMapper.xml | 19 ++++ 10 files changed, 303 insertions(+), 56 deletions(-) create mode 100644 src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java create mode 100644 src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java create mode 100644 src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java create mode 100644 src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java create mode 100644 src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java create mode 100644 src/main/resources/mapper/CategoryMapper.xml diff --git a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java new file mode 100644 index 0000000..e2847d5 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java @@ -0,0 +1,58 @@ +package com.barogagi.kakaoplace.client; + +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import com.barogagi.kakaoplace.dto.KakaoPlaceSearchResDTO; +import com.barogagi.schedule.command.service.ScheduleCommandService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.List; + +@Service +public class KakaoPlaceClient { + private static final Logger logger = LoggerFactory.getLogger(KakaoPlaceClient.class); + + @Value("${kakao.rest-api-key}") + private String kakaoApiKey; + + private final RestTemplate restTemplate = new RestTemplate(); + + public List searchKakaoPlace(String query, String x, String y, int radius, int limitPlace) { + String url = UriComponentsBuilder + .fromHttpUrl("https://dapi.kakao.com/v2/local/search/keyword.json") + .queryParam("query", query) + .queryParam("x", x) + .queryParam("y", y) + .queryParam("radius", radius) + .queryParam("size", limitPlace) + .toUriString(); + logger.info("#$# url={}", url); + + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "KakaoAK " + kakaoApiKey); + headers.set("Content-Type", "application/json;charset=UTF-8 "); + logger.info("#$# Request Headers: {}", headers); + + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + KakaoPlaceSearchResDTO.class + ); + logger.info("#$# response={}", response); + logger.info("#$# response={}", response.getBody()); + logger.info("#$# response={}", response.getBody().getDocuments()); + List body = response.getBody().getDocuments(); + return body; + } +} diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java new file mode 100644 index 0000000..45e6dbc --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java @@ -0,0 +1,44 @@ +package com.barogagi.kakaoplace.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "카카오 장소 추천 목록 DTO") +public class KakaoPlaceResDTO { + + @JsonProperty("address_name") + private String addressName; + + @JsonProperty("place_name") + private String placeName; + + @JsonProperty("category_group_name") + private String categoryGroupName; + + @JsonProperty("distance") + private String distance; + + @JsonProperty("id") + private String id; + + @JsonProperty("x") + private String x; + + @JsonProperty("y") + private String y; + + @JsonProperty("place_url") + private String placeUrl; + + @JsonProperty("road_address_name") + private String roadAddressName; + + @JsonProperty("phone") + private String phone; +} diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java new file mode 100644 index 0000000..1674ec1 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java @@ -0,0 +1,18 @@ +package com.barogagi.kakaoplace.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "카카오 장소 추천 목록 결과 DTO") +public class KakaoPlaceSearchResDTO { + @JsonProperty("documents") + private List documents; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java index 1b096b8..69507f2 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java @@ -15,8 +15,8 @@ @Schema(description = "계획 등록 요청 DTO") public class PlanRegistReqDTO { - @Schema(description = "계획 이름", example = "프랜차이즈카페") - public String planNm; + //@Schema(description = "계획 이름", example = "프랜차이즈카페") + //public String planNm; @Schema(description = "시작 시간", example = "08:00") public String startTime; @@ -30,15 +30,12 @@ public class PlanRegistReqDTO { @Schema(description = "카테고리 번호", example = "1") public int categoryNum; - @Schema(description = "추가 고려사항", example = "한식맛집") - private String comment; // 추가 고려사항 @Schema(description = "지역 정보 DTO") - public RegionRegistReqDTO regionRegistReqDTO; + public List regionRegistReqDTOList; - @ArraySchema(schema = @Schema(implementation = TagRegistReqDTO.class), - arraySchema = @Schema(description = "태그 리스트", example = "[{\"tagNum\":1,\"tagNm\":\"이색카페\"},{\"tagNum\":2,\"tagNm\":\"맛집투어\"}]")) - public List tagRegistReqDTOList; + @Schema(description = "태그 목록") + public List tagList; } diff --git a/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java b/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java new file mode 100644 index 0000000..f39d2bb --- /dev/null +++ b/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java @@ -0,0 +1,13 @@ +package com.barogagi.plan.query.mapper; + +import com.barogagi.plan.query.vo.PlanDetailVO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface CategoryMapper { + + // 카테고리명 조회 + String selectCategoryNmBy(int categoryNum); +} diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 4b4e3c6..b16c285 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -1,45 +1,84 @@ package com.barogagi.schedule.command.service; import com.barogagi.approval.mapper.ApprovalMapper; -//import com.barogagi.kakaoplace.client.KakaoPlaceClient; -//import com.barogagi.plan.dto.PlanRegistReqDTO; -//import com.barogagi.plan.query.mapper.CategoryMapper; -//import com.barogagi.schedule.dto.KakaoPlaceReqDTO; +import com.barogagi.kakaoplace.client.KakaoPlaceClient; +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import com.barogagi.member.join.controller.JoinController; +import com.barogagi.plan.dto.PlanRegistReqDTO; +import com.barogagi.plan.query.mapper.CategoryMapper; +import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.schedule.dto.KakaoPlaceReqDTO; import com.barogagi.schedule.dto.ScheduleRegistReqDTO; import com.barogagi.schedule.query.service.ScheduleQueryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; @Service public class ScheduleCommandService { -// private final CategoryMapper categoryMapper; -// private final KakaoPlaceClient kakaoPlaceClient; + private static final Logger logger = LoggerFactory.getLogger(ScheduleCommandService.class); -// @Autowired -// public ScheduleCommandService(CategoryMapper categoryMapper, -// KakaoPlaceClient kakaoPlaceClient){ -// this.categoryMapper = categoryMapper; -// this.kakaoPlaceClient = kakaoPlaceClient; -// } + private final CategoryMapper categoryMapper; + private final KakaoPlaceClient kakaoPlaceClient; + + + @Autowired + public ScheduleCommandService(CategoryMapper categoryMapper, + KakaoPlaceClient kakaoPlaceClient){ + this.categoryMapper = categoryMapper; + this.kakaoPlaceClient = kakaoPlaceClient; + } public void registSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { -// List planRegistReqDTOList = scheduleRegistReqDTO.getPlanRegistReqDTOList(); -// KakaoPlaceReqDTO kakaoPlaceReqDTO; -// -// // NaverBlogReqDTO naverBlogReqDTO; -// -// int radious = scheduleRegistReqDTO.getRadius(); -// int resultSize = 10; -// -// for (PlanRegistReqDTO plan : planRegistReqDTOList) { -// String queryString = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); // TODO. 검색 쿼리 고려 필요 -// String x = plan.getRegionRegistReqDTO().getX(); -// String y = plan.getRegionRegistReqDTO().getY(); -// -// kakaoPlaceClient.searchKakaoPlace(queryString, x, y, radious); -// } + List planRegistReqDTOList = scheduleRegistReqDTO.getPlanRegistReqDTOList(); + List> allKakaoPlaceResults = new ArrayList<>(); + + // NaverBlogReqDTO naverBlogReqDTO; + + int radious = scheduleRegistReqDTO.getRadius(); + int resultSize = 10; + + + // 1. 카카오 API로 추천 장소 리스트 조회 + for (PlanRegistReqDTO plan : planRegistReqDTOList) { + String queryString = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); // TODO. 검색 쿼리 고려 필요 + if (plan.getRegionRegistReqDTOList() != null && !plan.getRegionRegistReqDTOList().isEmpty()) { + + // 후보지역 수에 따라 각 지역의 후보장소 수를 조정 + int limitPlace = calLimitPlace(plan.getRegionRegistReqDTOList().size()); + + for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { + String x = region.getX(); + String y = region.getY(); + List kakaoPlaceResDTOList = kakaoPlaceClient.searchKakaoPlace(queryString, x, y, radious, limitPlace); + logger.info("kakaoPlaceResDTOList: {}", kakaoPlaceResDTOList); + } + } + } + + // 2. 네이버 블로그 API로 추천 장소에 대한 정보 조회 + + + // 3. AI API로 최종 추천 장소 조회 + + } + + // 후보지역 수에 따라 각 지역의 후보장소 수를 리턴 + // 후보장소 수만큼 네이버 블로그 API를 호출해야 하기 때문에 제한 필요 + private int calLimitPlace(int regionCount) { + int perRegionLimit; + if (regionCount == 1) { + perRegionLimit = 5; + } else if (regionCount == 2) { + perRegionLimit = 3; + } else { + perRegionLimit = 2; + } + return perRegionLimit; } } diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 12d2e59..bec1076 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -3,8 +3,10 @@ import com.barogagi.member.join.vo.JoinVO; import com.barogagi.member.join.vo.UserIdCheckVO; import com.barogagi.member.login.controller.LoginController; +import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.query.service.PlanQueryService; import com.barogagi.plan.query.vo.PlanDetailVO; +import com.barogagi.region.dto.RegionRegistReqDTO; import com.barogagi.response.ApiResponse; import com.barogagi.schedule.command.service.ScheduleCommandService; import com.barogagi.schedule.dto.ScheduleDetailResDTO; @@ -118,37 +120,56 @@ public ApiResponse registSchedule( examples = @ExampleObject( name = "일정 등록 요청 예시", value = "{\n" + - " \"scheduleNm\": \"서울 맛집 투어 일정\",\n" + + " \"scheduleNm\": \"서울 데이트 코스\",\n" + " \"startDate\": \"2025-07-01\",\n" + " \"endDate\": \"2025-07-01\",\n" + - " \"radius\": 3,\n" + - " \"planDetailVOList\": [\n" + + " \"radius\": 3000,\n" + + " \"comment\": \"분위기 좋은 카페 추천해주세요\",\n" + + " \"planRegistReqDTOList\": [\n" + " {\n" + " \"startTime\": \"08:00\",\n" + " \"endTime\": \"09:00\",\n" + - " \"itemNum\": 1,\n" + - " \"categoryNum\": 1,\n" + - " \"comment\": \"한식맛집\",\n" + + " \"itemNum\": 10,\n" + + " \"categoryNum\": 2,\n" + " \"regionRegistReqDTOList\": [\n" + " {\n" + - " \"regionNum\": 1,\n" + - " \"regionLevel1\": \"서울특별시\",\n" + - " \"regionLevel2\": \"강남구\",\n" + + " \"regionNm1\": \"서울특별시\",\n" + + " \"regionNm2\": \"강남구\",\n" + " \"x\": \"127.04892851392\",\n" + " \"y\": \"37.5091105328378\"\n" + - " },\n" + + " }\n" + + " ],\n" + + " \"tagList\": [1, 2]\n" + + " },\n" + + " {\n" + + " \"startTime\": \"14:00\",\n" + + " \"endTime\": \"15:00\",\n" + + " \"itemNum\": 2,\n" + + " \"categoryNum\": 1,\n" + + " \"regionRegistReqDTOList\": [\n" + " {\n" + - " \"regionNum\": 2,\n" + - " \"regionLevel1\": \"서울특별시\",\n" + - " \"regionLevel2\": \"송파구\",\n" + - " \"x\": \"127.09811980036908\",\n" + - " \"y\": \"37.51113059993883\"\n" + + " \"regionNm1\": \"서울특별시\",\n" + + " \"regionNm2\": \"강남구\",\n" + + " \"x\": \"127.046634887695\",\n" + + " \"y\": \"37.500690460205\"\n" + " }\n" + " ],\n" + - " \"tagRegistReqDTOList\": [\n" + - " { \"tagNum\": 1, \"tagNm\": \"디저트\" },\n" + - " { \"tagNum\": 2, \"tagNm\": \"야경\" }\n" + - " ]\n" + + " \"tagList\": [2]\n" + + " },\n" + + " {\n" + + " \"startTime\": \"15:30\",\n" + + " \"endTime\": \"19:00\",\n" + + " \"itemNum\": 15,\n" + + " \"categoryNum\": 4,\n" + + " \"regionRegistReqDTOList\": [\n" + + " {\n" + + " \"regionNm1\": \"서울특별시\",\n" + + " \"regionNm2\": \"종로구\",\n" + + " \"x\": \"126.996652\",\n" + + " \"y\": \"37.579617\"\n" + + " }\n" + + " ],\n" + + " \"tagList\": [2]\n" + " }\n" + " ]\n" + "}" @@ -159,6 +180,32 @@ public ApiResponse registSchedule( logger.info("CALL /schedule"); logger.info("[input] scheduleRegistReqDTO={}", scheduleRegistReqDTO.toString()); + for (int i = 0; i < scheduleRegistReqDTO.getPlanRegistReqDTOList().size(); i++) { + PlanRegistReqDTO plan = scheduleRegistReqDTO.getPlanRegistReqDTOList().get(i); + System.out.println("===== [Plan " + (i + 1) + "] ====="); + System.out.println("Start Time: " + plan.getStartTime()); + System.out.println("End Time : " + plan.getEndTime()); + System.out.println("Item Num : " + plan.getItemNum()); + System.out.println("Category : " + plan.getCategoryNum()); + + // 지역 정보 + if (plan.getRegionRegistReqDTOList() != null) { + for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { + System.out.println(" → Region: " + region.getRegionNm1() + " " + region.getRegionNm2() + + " (" + region.getX() + ", " + region.getY() + ")"); + } + } + + // 태그 번호 리스트 + if (plan.getTagList() != null) { + System.out.print("Tags : "); + for (int tagNum : plan.getTagList()) { + System.out.print(tagNum + " "); + } + System.out.println(); // 줄 바꿈 + } + System.out.println(); // Plan 구분용 빈 줄 + } ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -169,7 +216,7 @@ public ApiResponse registSchedule( //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ if (true) { boolean isVaildReq = true; // TODO. DTO 검증 로직 추가 필요 - if (isVaildReq) { + if (!isVaildReq) { resultCode = "101"; message = "입력값이 올바르지 않습니다."; } else { diff --git a/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java b/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java new file mode 100644 index 0000000..a12f0f7 --- /dev/null +++ b/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java @@ -0,0 +1,11 @@ +package com.barogagi.schedule.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class KakaoPlaceReqDTO { + @JsonProperty("address_name") + String addressName; // 상세주소 + + @JsonProperty("place_name") + String placeName; // 장소명 +} diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java index fdd01f2..2ec2251 100644 --- a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java @@ -17,9 +17,10 @@ public class ScheduleRegistReqDTO { private String startDate; // 시작 날짜 private String endDate; // 종료 날짜 private int radius; // 반경 + private String comment; // 추가 고려사항 // 지역 리스트 - private List regionRegistReqDTOList; + // private List regionRegistReqDTOList; // 계획 리스트 private List planRegistReqDTOList; diff --git a/src/main/resources/mapper/CategoryMapper.xml b/src/main/resources/mapper/CategoryMapper.xml new file mode 100644 index 0000000..7e71e6e --- /dev/null +++ b/src/main/resources/mapper/CategoryMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + From b4dc913d469f86aa45b249949ff8c75f4c6dd524 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Mon, 25 Aug 2025 22:27:06 +0900 Subject: [PATCH 04/79] =?UTF-8?q?feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EC=9E=A5=EC=86=8C=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kakaoplace/client/KakaoPlaceClient.java | 11 +++++----- .../dto/KakaoPlaceSearchResDTO.java | 1 + .../schedule/dto/KakaoPlaceReqDTO.java | 22 +++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java index e2847d5..db387ff 100644 --- a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java +++ b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java @@ -26,23 +26,22 @@ public class KakaoPlaceClient { private final RestTemplate restTemplate = new RestTemplate(); public List searchKakaoPlace(String query, String x, String y, int radius, int limitPlace) { - String url = UriComponentsBuilder + String url = String.valueOf(UriComponentsBuilder .fromHttpUrl("https://dapi.kakao.com/v2/local/search/keyword.json") .queryParam("query", query) .queryParam("x", x) .queryParam("y", y) .queryParam("radius", radius) .queryParam("size", limitPlace) - .toUriString(); + .build(false)); + logger.info("#$# url={}", url); HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "KakaoAK " + kakaoApiKey); - headers.set("Content-Type", "application/json;charset=UTF-8 "); logger.info("#$# Request Headers: {}", headers); HttpEntity entity = new HttpEntity<>(headers); - ResponseEntity response = restTemplate.exchange( url, HttpMethod.GET, @@ -50,8 +49,8 @@ public List searchKakaoPlace(String query, String x, String y, KakaoPlaceSearchResDTO.class ); logger.info("#$# response={}", response); - logger.info("#$# response={}", response.getBody()); - logger.info("#$# response={}", response.getBody().getDocuments()); + logger.info("#$# response.getDocuments={}", response.getBody().getDocuments()); + List body = response.getBody().getDocuments(); return body; } diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java index 1674ec1..2aa7f50 100644 --- a/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java @@ -1,6 +1,7 @@ package com.barogagi.kakaoplace.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; diff --git a/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java b/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java index a12f0f7..dbb3384 100644 --- a/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java +++ b/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java @@ -1,11 +1,11 @@ -package com.barogagi.schedule.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; - -public class KakaoPlaceReqDTO { - @JsonProperty("address_name") - String addressName; // 상세주소 - - @JsonProperty("place_name") - String placeName; // 장소명 -} +//package com.barogagi.schedule.dto; +// +//import com.fasterxml.jackson.annotation.JsonProperty; +// +//public class KakaoPlaceReqDTO { +// @JsonProperty("address_name") +// String addressName; // 상세주소 +// +// @JsonProperty("place_name") +// String placeName; // 장소명 +//} From e37a786121fb8f133d7e25e31c981f1152932236 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Mon, 25 Aug 2025 22:27:22 +0900 Subject: [PATCH 05/79] =?UTF-8?q?feat:=20=EB=84=A4=EC=9D=B4=EB=B2=84=20?= =?UTF-8?q?=EB=B8=94=EB=A1=9C=EA=B7=B8=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../naverbolg/client/NaverBlogClient.java | 62 +++++++++++++++++++ .../naverbolg/dto/NaverBlogResDTO.java | 35 +++++++++++ .../naverbolg/dto/NaverBlogSearchResDTO.java | 31 ++++++++++ 3 files changed, 128 insertions(+) create mode 100644 src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java create mode 100644 src/main/java/com/barogagi/naverbolg/dto/NaverBlogResDTO.java create mode 100644 src/main/java/com/barogagi/naverbolg/dto/NaverBlogSearchResDTO.java diff --git a/src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java b/src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java new file mode 100644 index 0000000..f36a2d5 --- /dev/null +++ b/src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java @@ -0,0 +1,62 @@ +package com.barogagi.naverbolg.client; + +import com.barogagi.kakaoplace.client.KakaoPlaceClient; +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import com.barogagi.kakaoplace.dto.KakaoPlaceSearchResDTO; +import com.barogagi.naverbolg.dto.NaverBlogResDTO; +import com.barogagi.naverbolg.dto.NaverBlogSearchResDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.List; + +@Service +public class NaverBlogClient { + private static final Logger logger = LoggerFactory.getLogger(NaverBlogClient.class); + + @Value("${naver.x-naver-client-id}") + private String xNaverClientId; + + @Value("${naver.x-naver-client-secret}") + private String xNaverClientSecret; + + private final RestTemplate restTemplate = new RestTemplate(); + // https://openapi.naver.com/v1/search/blog.json?query=고궁의아침 강남구&display=5 + + public List searchNaverBlog(String query, int display) { + String url = String.valueOf(UriComponentsBuilder + .fromHttpUrl("https://openapi.naver.com/v1/search/blog.json") + .queryParam("query", query) + .queryParam("display", display) + .build(false)); + logger.info("#$# url={}", url); + + HttpHeaders headers = new HttpHeaders(); + headers.set("X-Naver-Client-Id", xNaverClientId); + headers.set("X-Naver-Client-Secret", xNaverClientSecret); + //headers.set("Content-Type", "application/json;charset=UTF-8 "); + logger.info("#$# Request Headers: {}", headers); + + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + NaverBlogSearchResDTO.class + ); + logger.info("#$# response.getBody()={}", response.getBody()); + logger.info("#$# response.getBody().getItems()={}", response.getBody().getItems()); + List body = response.getBody().getItems(); + + return body; + } + +} diff --git a/src/main/java/com/barogagi/naverbolg/dto/NaverBlogResDTO.java b/src/main/java/com/barogagi/naverbolg/dto/NaverBlogResDTO.java new file mode 100644 index 0000000..e24be91 --- /dev/null +++ b/src/main/java/com/barogagi/naverbolg/dto/NaverBlogResDTO.java @@ -0,0 +1,35 @@ +package com.barogagi.naverbolg.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.util.List; + +@Data +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "네이버 블로그 검색 결과 DTO") +public class NaverBlogResDTO { + @JsonProperty("title") + private String title; + + @JsonProperty("link") + private String link; + + @JsonProperty("description") + private String description; + + @JsonProperty("bloggername") + private String bloggerName; + + @JsonProperty("bloggerlink") + private String bloggerLink; + + @JsonProperty("postdate") + private String postDate; + +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/naverbolg/dto/NaverBlogSearchResDTO.java b/src/main/java/com/barogagi/naverbolg/dto/NaverBlogSearchResDTO.java new file mode 100644 index 0000000..aefa8e8 --- /dev/null +++ b/src/main/java/com/barogagi/naverbolg/dto/NaverBlogSearchResDTO.java @@ -0,0 +1,31 @@ +package com.barogagi.naverbolg.dto; + +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "네이버 블로그 검색 결과 DTO") +public class NaverBlogSearchResDTO { + @JsonProperty("lastBuildDate") + private String lastBuildDate; + + @JsonProperty("total") + private int total; + + @JsonProperty("start") + private int start; + + @JsonProperty("display") + private int display; + + @JsonProperty("items") + private List items; +} \ No newline at end of file From 052fcc6435f9b3fde2798025d45723b648764770 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Mon, 25 Aug 2025 22:27:43 +0900 Subject: [PATCH 06/79] =?UTF-8?q?feat:=20=ED=83=9C=EA=B7=B8=EB=B2=88?= =?UTF-8?q?=ED=98=B8=EB=A1=9C=20=ED=83=9C=EA=B7=B8=EB=AA=85=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../barogagi/tag/query/mapper/TagMapper.java | 3 ++ .../tag/query/service/TagQueryService.java | 33 +++++++++++++++++++ src/main/resources/mapper/TagMapper.xml | 10 ++++++ 3 files changed, 46 insertions(+) create mode 100644 src/main/java/com/barogagi/tag/query/service/TagQueryService.java diff --git a/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java b/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java index 42005f8..1aa7eb2 100644 --- a/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java +++ b/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java @@ -10,4 +10,7 @@ public interface TagMapper { // 계획 상세 조회 - 태그 상세 조회 List selectTagByPlanNum (int planNum); + + // 일정 생성 - 태그 번호로 태그명 조회 + TagDetailVO selectTagByTagNum (int tagNum); } diff --git a/src/main/java/com/barogagi/tag/query/service/TagQueryService.java b/src/main/java/com/barogagi/tag/query/service/TagQueryService.java new file mode 100644 index 0000000..3612ef6 --- /dev/null +++ b/src/main/java/com/barogagi/tag/query/service/TagQueryService.java @@ -0,0 +1,33 @@ +package com.barogagi.tag.query.service; + +import com.barogagi.tag.query.mapper.TagMapper; +import com.barogagi.tag.query.vo.TagDetailVO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.stream.Collectors; + +import java.util.List; + +@Service +public class TagQueryService { + + private final TagMapper tagMapper; + + @Autowired + public TagQueryService (TagMapper tagMapper) { + this.tagMapper = tagMapper; + } + + // 계획 번호로 연결된 태그 상세 리스트 조회 + public List findTagByPlanNum(int planNum) { + return tagMapper.selectTagByPlanNum(planNum); + } + + // 태그 번호 리스트로 태그명 리스트 조회 + public List findTagNmByTagNum(List tagNums) { + return tagNums.stream() + .map(tagMapper::selectTagByTagNum) + .map(TagDetailVO::getTagNm) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/resources/mapper/TagMapper.xml b/src/main/resources/mapper/TagMapper.xml index 49eefc9..1fd84cd 100644 --- a/src/main/resources/mapper/TagMapper.xml +++ b/src/main/resources/mapper/TagMapper.xml @@ -16,4 +16,14 @@ WHERE a.PLAN_NUM = #{planNum}; ]]> + + From 602da285a6c38bfb6851b52b22ab727a869c878a Mon Sep 17 00:00:00 2001 From: RohDamin Date: Mon, 25 Aug 2025 22:28:13 +0900 Subject: [PATCH 07/79] =?UTF-8?q?feat:=20ai=20=EC=B6=94=EC=B2=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A7=84=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/barogagi/ai/client/AIClient.java | 92 +++++++++++++++++++ .../java/com/barogagi/ai/dto/AIReqDTO.java | 13 +++ .../com/barogagi/ai/dto/AIReqWrapper.java | 15 +++ .../java/com/barogagi/ai/dto/AIResDTO.java | 16 ++++ .../java/com/barogagi/ai/dto/ChatMessage.java | 13 +++ .../java/com/barogagi/ai/dto/ChatRequest.java | 16 ++++ .../java/com/barogagi/util/HtmlUtils.java | 22 +++++ 7 files changed, 187 insertions(+) create mode 100644 src/main/java/com/barogagi/ai/client/AIClient.java create mode 100644 src/main/java/com/barogagi/ai/dto/AIReqDTO.java create mode 100644 src/main/java/com/barogagi/ai/dto/AIReqWrapper.java create mode 100644 src/main/java/com/barogagi/ai/dto/AIResDTO.java create mode 100644 src/main/java/com/barogagi/ai/dto/ChatMessage.java create mode 100644 src/main/java/com/barogagi/ai/dto/ChatRequest.java create mode 100644 src/main/java/com/barogagi/util/HtmlUtils.java diff --git a/src/main/java/com/barogagi/ai/client/AIClient.java b/src/main/java/com/barogagi/ai/client/AIClient.java new file mode 100644 index 0000000..b930ab5 --- /dev/null +++ b/src/main/java/com/barogagi/ai/client/AIClient.java @@ -0,0 +1,92 @@ +package com.barogagi.ai.client; + +import com.barogagi.ai.dto.AIReqWrapper; +import com.barogagi.ai.dto.AIResDTO; +import com.barogagi.ai.dto.ChatMessage; +import com.barogagi.ai.dto.ChatRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class AIClient { + private static final Logger logger = LoggerFactory.getLogger(AIClient.class); + + @Value("${ai.api.base-url}") + private String aiBaseUrl; + + @Value("${ai.api.path}") + private String aiPath; + + @Value("${ai.api.key}") + private String aiApiKey; + + @Value("${ai.prompt.system}") + private String aiPrompt; + + @Value("${ai.model}") + private String aiModel; + + private final RestTemplate restTemplate = new RestTemplate(); + + public AIResDTO recommandPlace(AIReqWrapper aiReqWrapper) { + String url = aiBaseUrl + aiPath; + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(aiApiKey); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + + // 변환 + ChatRequest chatRequest = buildChatRequest(aiReqWrapper); + + HttpEntity entity = new HttpEntity<>(chatRequest, headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.POST, + entity, + AIResDTO.class + ); + + return response.getBody(); + } + + // ai에게 요청 가능한 형태로 변경 + private ChatRequest buildChatRequest(AIReqWrapper wrapper) { + // system 메시지 + ChatMessage systemMsg = ChatMessage.builder() + .role("system") + .content(aiPrompt) + .build(); + + // user 메시지: AIReqWrapper → 문자열 변환 + StringBuilder sb = new StringBuilder(); + sb.append("comment: ").append(wrapper.getComment()).append("\n"); + sb.append("tags: ").append(String.join(", ", wrapper.getTags())).append("\n\n"); + + sb.append("places:\n"); + String placesText = wrapper.getPlaceList().stream() + .map(p -> "- title: " + p.getTitle() + "\n description: " + p.getDescription()) + .collect(Collectors.joining("\n")); + sb.append(placesText); + + ChatMessage userMsg = ChatMessage.builder() + .role("user") + .content(sb.toString()) + .build(); + + return ChatRequest.builder() + .model(aiModel) + .messages(List.of(systemMsg, userMsg)) + .max_tokens(500) + .build(); + } +} diff --git a/src/main/java/com/barogagi/ai/dto/AIReqDTO.java b/src/main/java/com/barogagi/ai/dto/AIReqDTO.java new file mode 100644 index 0000000..95f8fbc --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/AIReqDTO.java @@ -0,0 +1,13 @@ +package com.barogagi.ai.dto; + +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class AIReqDTO { + private String title; + private String description; +} diff --git a/src/main/java/com/barogagi/ai/dto/AIReqWrapper.java b/src/main/java/com/barogagi/ai/dto/AIReqWrapper.java new file mode 100644 index 0000000..53fc097 --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/AIReqWrapper.java @@ -0,0 +1,15 @@ +package com.barogagi.ai.dto; +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class AIReqWrapper { + private List tags; + private String comment; + private List placeList; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/ai/dto/AIResDTO.java b/src/main/java/com/barogagi/ai/dto/AIResDTO.java new file mode 100644 index 0000000..99b1d63 --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/AIResDTO.java @@ -0,0 +1,16 @@ +package com.barogagi.ai.dto; + +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class AIResDTO { +// private String requestId; +// private String model; +// private String content; + private Integer recommandPlaceNum; + private String aiDescription; +} diff --git a/src/main/java/com/barogagi/ai/dto/ChatMessage.java b/src/main/java/com/barogagi/ai/dto/ChatMessage.java new file mode 100644 index 0000000..2042ec6 --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/ChatMessage.java @@ -0,0 +1,13 @@ +package com.barogagi.ai.dto; + +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class ChatMessage { + private String role; // "system" | "user" | "assistant" + private String content; // 메시지 내용 +} diff --git a/src/main/java/com/barogagi/ai/dto/ChatRequest.java b/src/main/java/com/barogagi/ai/dto/ChatRequest.java new file mode 100644 index 0000000..61f9355 --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/ChatRequest.java @@ -0,0 +1,16 @@ +package com.barogagi.ai.dto; + +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class ChatRequest { + private String model; + private List messages; + private int max_tokens; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/util/HtmlUtils.java b/src/main/java/com/barogagi/util/HtmlUtils.java new file mode 100644 index 0000000..0112d8d --- /dev/null +++ b/src/main/java/com/barogagi/util/HtmlUtils.java @@ -0,0 +1,22 @@ +package com.barogagi.util; + +import java.util.regex.Pattern; + +public class HtmlUtils { + + private static final Pattern HTML_TAG = Pattern.compile("<[^>]*>"); + + private HtmlUtils() { + // util 클래스는 인스턴스 생성 방지 + } + + public static String stripHtml(String s) { + if (s == null || s.isBlank()) return ""; + // 1) 태그 제거 + String t = HTML_TAG.matcher(s).replaceAll(" "); + // 2) 엔티티 정리 + t = t.replace(" ", " "); + // 3) 공백 정리 + return t.replaceAll("\\s+", " ").trim(); + } +} From ac931b27b4f08ec2777c67297addeafc30ad7b66 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Mon, 25 Aug 2025 22:28:28 +0900 Subject: [PATCH 08/79] =?UTF-8?q?feat:=20ai=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EA=B8=B0=EB=8A=A5=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ScheduleCommandService.java | 100 +++++++++++++++--- .../controller/ScheduleController.java | 27 ----- 2 files changed, 88 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index b16c285..4eca5d6 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -1,22 +1,32 @@ package com.barogagi.schedule.command.service; -import com.barogagi.approval.mapper.ApprovalMapper; +import com.barogagi.ai.client.AIClient; +import com.barogagi.ai.dto.AIReqDTO; +import com.barogagi.ai.dto.AIReqWrapper; +import com.barogagi.ai.dto.AIResDTO; import com.barogagi.kakaoplace.client.KakaoPlaceClient; import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; -import com.barogagi.member.join.controller.JoinController; +import com.barogagi.naverbolg.client.NaverBlogClient; +import com.barogagi.naverbolg.dto.NaverBlogResDTO; import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.query.mapper.CategoryMapper; import com.barogagi.region.dto.RegionRegistReqDTO; -import com.barogagi.schedule.dto.KakaoPlaceReqDTO; import com.barogagi.schedule.dto.ScheduleRegistReqDTO; -import com.barogagi.schedule.query.service.ScheduleQueryService; +import com.barogagi.tag.query.service.TagQueryService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.regex.Pattern; + +import static com.barogagi.util.HtmlUtils.stripHtml; @Service public class ScheduleCommandService { @@ -24,13 +34,25 @@ public class ScheduleCommandService { private final CategoryMapper categoryMapper; private final KakaoPlaceClient kakaoPlaceClient; + private final NaverBlogClient naverBlogClient; + private final AIClient aiClient; + + private final TagQueryService tagQueryService; + + @Value("${naver.display}") + private int naverBlogDisplay; + @Autowired public ScheduleCommandService(CategoryMapper categoryMapper, - KakaoPlaceClient kakaoPlaceClient){ + KakaoPlaceClient kakaoPlaceClient, NaverBlogClient naverBlogClient, + AIClient aiClient, TagQueryService tagQueryService) { this.categoryMapper = categoryMapper; this.kakaoPlaceClient = kakaoPlaceClient; + this.naverBlogClient = naverBlogClient; + this.aiClient = aiClient; + this.tagQueryService = tagQueryService; } public void registSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { @@ -38,15 +60,17 @@ public void registSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { List planRegistReqDTOList = scheduleRegistReqDTO.getPlanRegistReqDTOList(); List> allKakaoPlaceResults = new ArrayList<>(); - // NaverBlogReqDTO naverBlogReqDTO; + List> allNaverBlogResults = new ArrayList<>(); + - int radious = scheduleRegistReqDTO.getRadius(); - int resultSize = 10; + int radius = scheduleRegistReqDTO.getRadius(); - // 1. 카카오 API로 추천 장소 리스트 조회 for (PlanRegistReqDTO plan : planRegistReqDTOList) { + // 1. 카카오 API로 추천 장소 리스트 조회 String queryString = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); // TODO. 검색 쿼리 고려 필요 + List kakaoPlaceResDTOList = new ArrayList<>(); + if (plan.getRegionRegistReqDTOList() != null && !plan.getRegionRegistReqDTOList().isEmpty()) { // 후보지역 수에 따라 각 지역의 후보장소 수를 조정 @@ -55,16 +79,68 @@ public void registSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { String x = region.getX(); String y = region.getY(); - List kakaoPlaceResDTOList = kakaoPlaceClient.searchKakaoPlace(queryString, x, y, radious, limitPlace); + kakaoPlaceResDTOList = kakaoPlaceClient.searchKakaoPlace(queryString, x, y, radius, limitPlace); logger.info("kakaoPlaceResDTOList: {}", kakaoPlaceResDTOList); } } + // 2. 네이버 블로그 API로 추천 장소에 대한 정보 조회 + List naverBlogResDTO = new ArrayList<>(); + for(KakaoPlaceResDTO kakaoPlace : kakaoPlaceResDTOList) { + String query = kakaoPlace.getPlaceName() + " " + kakaoPlace.getRoadAddressName(); // TODO. 검색 쿼리 고려 필요 + naverBlogResDTO = naverBlogClient.searchNaverBlog(query, naverBlogDisplay); + logger.info("naverBlogResDTO: {}", naverBlogResDTO); + allNaverBlogResults.add(naverBlogResDTO); + } + + // 3. AI API로 최종 추천 장소 조회 + + // 3-1. 태그 이름 조회 + List tagNums = scheduleRegistReqDTO.getPlanRegistReqDTOList().stream() + .filter(Objects::nonNull) + .flatMap(p -> p.getTagList().stream()) + .collect(Collectors.toList()); + logger.info("#$# tagNums size={}, values={}", tagNums.size(), tagNums); + List tagNames = tagQueryService.findTagNmByTagNum(tagNums); + logger.info("#$# tagNames size={}, values={}", tagNames.size(), tagNames); + + // 3-2. 네이버 블로그 결과를 title/description으로 변환 + List placeList = allNaverBlogResults.stream() + .flatMap(List::stream) + .map(b -> AIReqDTO.builder() + .title(stripHtml(b.getTitle())) + .description(stripHtml(b.getDescription())) + .build()) + .collect(Collectors.toList()); + logger.info("#$# placeList size={}, sample={}", placeList.size(), + placeList.stream().limit(3).collect(Collectors.toList())); + + // 3-3. AI 요청 래퍼 구성 + AIReqWrapper aiReqWrapper = AIReqWrapper.builder() + .tags(tagNames) + .comment(Optional.ofNullable(scheduleRegistReqDTO.getComment()).orElse("")) + .placeList(placeList) + .build(); + + logger.info("#$# AIReqWrapper ready: tags={}, comment.len={}, placeList.size={}", + aiReqWrapper.getTags(), + aiReqWrapper.getComment() == null ? 0 : aiReqWrapper.getComment().length(), + aiReqWrapper.getPlaceList().size()); + + // 4) AI 호출 + AIResDTO aiRes = aiClient.recommandPlace(aiReqWrapper); + logger.info("#$# AI Recommendation result obj={}", aiRes); + if (aiRes != null) { + logger.info("#$# AI result fields: recommandPlaceNum={}, aiDescription={}", + aiRes.getRecommandPlaceNum(), aiRes.getAiDescription()); + } + + + } + logger.info("allNaverBlogResults: {}", allNaverBlogResults); - // 2. 네이버 블로그 API로 추천 장소에 대한 정보 조회 - // 3. AI API로 최종 추천 장소 조회 } diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index bec1076..b7a732b 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -179,33 +179,6 @@ public ApiResponse registSchedule( @RequestBody ScheduleRegistReqDTO scheduleRegistReqDTO) { logger.info("CALL /schedule"); - logger.info("[input] scheduleRegistReqDTO={}", scheduleRegistReqDTO.toString()); - for (int i = 0; i < scheduleRegistReqDTO.getPlanRegistReqDTOList().size(); i++) { - PlanRegistReqDTO plan = scheduleRegistReqDTO.getPlanRegistReqDTOList().get(i); - System.out.println("===== [Plan " + (i + 1) + "] ====="); - System.out.println("Start Time: " + plan.getStartTime()); - System.out.println("End Time : " + plan.getEndTime()); - System.out.println("Item Num : " + plan.getItemNum()); - System.out.println("Category : " + plan.getCategoryNum()); - - // 지역 정보 - if (plan.getRegionRegistReqDTOList() != null) { - for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { - System.out.println(" → Region: " + region.getRegionNm1() + " " + region.getRegionNm2() + - " (" + region.getX() + ", " + region.getY() + ")"); - } - } - - // 태그 번호 리스트 - if (plan.getTagList() != null) { - System.out.print("Tags : "); - for (int tagNum : plan.getTagList()) { - System.out.print(tagNum + " "); - } - System.out.println(); // 줄 바꿈 - } - System.out.println(); // Plan 구분용 빈 줄 - } ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; From fdf4c5a2cc7d5b25c12baa90e118a98740f21165 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Tue, 26 Aug 2025 23:17:21 +0900 Subject: [PATCH 09/79] =?UTF-8?q?feat:=20ai=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EC=B2=9C=20=EA=B2=B0=EA=B3=BC=20=EB=A6=AC=ED=84=B4?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/barogagi/ai/client/AIClient.java | 70 ++++++- .../barogagi/plan/command/entity/Plan.java | 10 + .../barogagi/plan/dto/PlanRegistReqDTO.java | 2 - .../barogagi/plan/dto/PlanRegistResDTO.java | 56 ++++++ .../service/ScheduleCommandService.java | 183 ++++++++++++------ .../controller/ScheduleController.java | 7 +- .../schedule/dto/ScheduleRegistResDTO.java | 23 +++ .../java/com/barogagi/util/HtmlUtils.java | 15 ++ 8 files changed, 296 insertions(+), 70 deletions(-) create mode 100644 src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java create mode 100644 src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java diff --git a/src/main/java/com/barogagi/ai/client/AIClient.java b/src/main/java/com/barogagi/ai/client/AIClient.java index b930ab5..5ef0168 100644 --- a/src/main/java/com/barogagi/ai/client/AIClient.java +++ b/src/main/java/com/barogagi/ai/client/AIClient.java @@ -4,17 +4,20 @@ import com.barogagi.ai.dto.AIResDTO; import com.barogagi.ai.dto.ChatMessage; import com.barogagi.ai.dto.ChatRequest; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; import java.util.List; import java.util.stream.Collectors; +import static com.barogagi.util.HtmlUtils.stripCodeFence; + @Service public class AIClient { private static final Logger logger = LoggerFactory.getLogger(AIClient.class); @@ -35,6 +38,7 @@ public class AIClient { private String aiModel; private final RestTemplate restTemplate = new RestTemplate(); + private static final ObjectMapper OM = new ObjectMapper(); public AIResDTO recommandPlace(AIReqWrapper aiReqWrapper) { String url = aiBaseUrl + aiPath; @@ -44,21 +48,75 @@ public AIResDTO recommandPlace(AIReqWrapper aiReqWrapper) { headers.setContentType(MediaType.APPLICATION_JSON); headers.setAccept(List.of(MediaType.APPLICATION_JSON)); - // 변환 ChatRequest chatRequest = buildChatRequest(aiReqWrapper); - HttpEntity entity = new HttpEntity<>(chatRequest, headers); - ResponseEntity response = restTemplate.exchange( + ResponseEntity response = restTemplate.exchange( url, HttpMethod.POST, entity, - AIResDTO.class + String.class ); - return response.getBody(); + String body = response.getBody(); + + ObjectMapper om = new ObjectMapper(); + try { + // 1) content 추출 + JsonNode root = om.readTree(body); + JsonNode contentNode = root.path("choices").get(0).path("message").path("content"); + String content = contentNode.asText(); + + // 2) 코드펜스/여분 공백 제거 + content = stripCodeFence(content); + + // 3) content(JSON 문자열) -> DTO + AIResDTO dto = om.readValue(content, AIResDTO.class); + return dto; + + } catch (com.fasterxml.jackson.core.JsonProcessingException e) { + // JsonMappingException 포함해서 모두 여기서 처리됨 + return null; // 혹은 throw new IllegalStateException("AI 응답 파싱 실패", e); + } } +// String url = aiBaseUrl + aiPath; +// +// HttpHeaders headers = new HttpHeaders(); +// headers.setBearerAuth(aiApiKey); +// headers.setContentType(MediaType.APPLICATION_JSON); +// headers.setAccept(List.of(MediaType.APPLICATION_JSON)); +// +// ChatRequest chatRequest = buildChatRequest(aiReqWrapper); +// HttpEntity entity = new HttpEntity<>(chatRequest, headers); +// +// // --- 최소 요청 로그 --- +// logger.info("#$# AI url={}", url); +// logger.info("#$# AI req: tags={}, commentLen={}, placeList={}", +// aiReqWrapper.getTags() == null ? 0 : aiReqWrapper.getTags().size(), +// aiReqWrapper.getComment() == null ? 0 : aiReqWrapper.getComment().length(), +// aiReqWrapper.getPlaceList() == null ? 0 : aiReqWrapper.getPlaceList().size()); +// +// // --- 응답 원문(String)으로 1회 호출 → 로그 찍고 → DTO로 매핑 --- +// ResponseEntity resp = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); +// logger.info("#$# AI resp status={}", resp.getStatusCode()); +// String raw = resp.getBody(); +// logger.info("#$# AI resp raw (preview): {}", raw == null +// ? "null" +// : (raw.length() <= 1000 ? raw : raw.substring(0, 1000) + "...(truncated)")); +// +// // --- DTO 매핑 (실패해도 예외만 로그) --- +// try { +// AIResDTO dto = OM.readValue(raw, AIResDTO.class); +// logger.info("#$# AI resp DTO: recommandPlaceNum={}, aiDescription='{}'", +// dto.getRecommandPlaceNum(), dto.getAiDescription()); +// return dto; +// } catch (Exception e) { +// logger.warn("#$# parse to AIResDTO failed: {}", e.getMessage()); +// return null; +// } +// } + // ai에게 요청 가능한 형태로 변경 private ChatRequest buildChatRequest(AIReqWrapper wrapper) { // system 메시지 diff --git a/src/main/java/com/barogagi/plan/command/entity/Plan.java b/src/main/java/com/barogagi/plan/command/entity/Plan.java index a824330..c71ad9a 100644 --- a/src/main/java/com/barogagi/plan/command/entity/Plan.java +++ b/src/main/java/com/barogagi/plan/command/entity/Plan.java @@ -2,6 +2,7 @@ import com.barogagi.plan.command.ex_entity.PlanUserMembershipInfo; import com.barogagi.schedule.command.entity.Schedule; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.*; import lombok.*; @@ -27,6 +28,15 @@ public class Plan { @Column(name = "END_TIME") private String endTime; // 종료 시간 + @Column(name = "PLAN_LINK") + public String planLink; // 장소 링크 (이미지 불러오기용) + + @Column(name = "PLAN_DESCRIPTION") + private String planDescription; // 장소 한줄 설명(AI 생성) + + @Column(name = "PLAN_ADDRESS") + private String planAddress; // 장소 주소 + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "SCHEDULE_NUM", nullable = false) private Schedule schedule; // 일정 번호 diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java index 69507f2..adba183 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java @@ -30,8 +30,6 @@ public class PlanRegistReqDTO { @Schema(description = "카테고리 번호", example = "1") public int categoryNum; - - @Schema(description = "지역 정보 DTO") public List regionRegistReqDTOList; diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java new file mode 100644 index 0000000..485f314 --- /dev/null +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java @@ -0,0 +1,56 @@ +package com.barogagi.plan.dto; + +import com.barogagi.region.dto.RegionRegistReqDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +@Builder(toBuilder = true) +@Schema(description = "계획 등록 응답 DTO") +public class PlanRegistResDTO { + + @Schema(description = "시작 시간", example = "08:00") + public String startTime; + + @Schema(description = "종료 시간", example = "09:00") + public String endTime; + + @Schema(description = "아이템 번호", example = "1") + public int itemNum; + + @Schema(description = "아이템 명", example = "1") + public String itemNm; + + @Schema(description = "카테고리 번호", example = "1") + public int categoryNum; + + @Schema(description = "카테고리 명", example = "1") + public String categoryNm; + + @Schema(description = "장소 번호") + public String planNum; + + @Schema(description = "장소명") + public String planNm; + + @Schema(description = "장소 링크(이미지 불러오기용)") + public String planLink; + + @Schema(description = "장소 한줄 설명(ai 생성)") + public String planDescription; + + @Schema(description = "장소 주소") + public String planAddress; + + @Schema(description = "지역 정보 DTO") + public String regionName; + + @Schema(description = "태그 목록") + public List tagList; +} + diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 4eca5d6..e311917 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -9,16 +9,20 @@ import com.barogagi.naverbolg.client.NaverBlogClient; import com.barogagi.naverbolg.dto.NaverBlogResDTO; import com.barogagi.plan.dto.PlanRegistReqDTO; +import com.barogagi.plan.dto.PlanRegistResDTO; import com.barogagi.plan.query.mapper.CategoryMapper; import com.barogagi.region.dto.RegionRegistReqDTO; import com.barogagi.schedule.dto.ScheduleRegistReqDTO; +import com.barogagi.schedule.dto.ScheduleRegistResDTO; import com.barogagi.tag.query.service.TagQueryService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -55,95 +59,149 @@ public ScheduleCommandService(CategoryMapper categoryMapper, this.tagQueryService = tagQueryService; } - public void registSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { + @Transactional + public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { - List planRegistReqDTOList = scheduleRegistReqDTO.getPlanRegistReqDTOList(); - List> allKakaoPlaceResults = new ArrayList<>(); + List planResList = new ArrayList<>(); - List> allNaverBlogResults = new ArrayList<>(); + // 스케줄 공통 정보 + String scheduleNm = scheduleRegistReqDTO.getScheduleNm(); + String startDate = scheduleRegistReqDTO.getStartDate(); + String endDate = scheduleRegistReqDTO.getEndDate(); + for (PlanRegistReqDTO plan : scheduleRegistReqDTO.getPlanRegistReqDTOList()) { + int radius = scheduleRegistReqDTO.getRadius(); - int radius = scheduleRegistReqDTO.getRadius(); + // ---------- 1) Kakao 후보장소 수집(평탄화) ---------- + List> allKakaoPlaceResults = new ArrayList<>(); + String queryString = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); // 검색어 + if (plan.getRegionRegistReqDTOList() == null || plan.getRegionRegistReqDTOList().isEmpty()) { + // 지역이 없으면 스킵 + logger.info("#$# skip: plan has no regions. plan={}", plan); + continue; + } - for (PlanRegistReqDTO plan : planRegistReqDTOList) { - // 1. 카카오 API로 추천 장소 리스트 조회 - String queryString = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); // TODO. 검색 쿼리 고려 필요 - List kakaoPlaceResDTOList = new ArrayList<>(); + int limitPlace = calLimitPlace(plan.getRegionRegistReqDTOList().size()); + for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { + List oneRegionPlaces = + kakaoPlaceClient.searchKakaoPlace(queryString, region.getX(), region.getY(), radius, limitPlace); + allKakaoPlaceResults.add(oneRegionPlaces); + } + logger.info("allKakaoPlaceResults: {}", allKakaoPlaceResults); - if (plan.getRegionRegistReqDTOList() != null && !plan.getRegionRegistReqDTOList().isEmpty()) { + // Kakao 평탄화(이 순서를 기준으로 이후 Naver/AI도 동일하게 맞춤) + List flatKakao = allKakaoPlaceResults.stream() + .filter(Objects::nonNull) + .flatMap(List::stream) + .collect(Collectors.toList()); - // 후보지역 수에 따라 각 지역의 후보장소 수를 조정 - int limitPlace = calLimitPlace(plan.getRegionRegistReqDTOList().size()); + if (flatKakao.isEmpty()) { + logger.info("#$# no kakao results. plan={}", plan); + continue; + } - for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { - String x = region.getX(); - String y = region.getY(); - kakaoPlaceResDTOList = kakaoPlaceClient.searchKakaoPlace(queryString, x, y, radius, limitPlace); - logger.info("kakaoPlaceResDTOList: {}", kakaoPlaceResDTOList); + // ---------- 2) Naver 블로그로 title/description 만들기 ---------- + List allBlogsFlat = new ArrayList<>(); + for (KakaoPlaceResDTO k : flatKakao) { + String query = k.getPlaceName() + " " + k.getRoadAddressName(); + List blogs = naverBlogClient.searchNaverBlog(query, naverBlogDisplay); + if (blogs != null) { + allBlogsFlat.addAll(blogs); } } - // 2. 네이버 블로그 API로 추천 장소에 대한 정보 조회 - List naverBlogResDTO = new ArrayList<>(); - for(KakaoPlaceResDTO kakaoPlace : kakaoPlaceResDTOList) { - String query = kakaoPlace.getPlaceName() + " " + kakaoPlace.getRoadAddressName(); // TODO. 검색 쿼리 고려 필요 - naverBlogResDTO = naverBlogClient.searchNaverBlog(query, naverBlogDisplay); - logger.info("naverBlogResDTO: {}", naverBlogResDTO); - allNaverBlogResults.add(naverBlogResDTO); - } + logger.info("allNaverBlogResults.size={}", allBlogsFlat.size()); + + // AI placeList: Kakao 후보 1:1이 가장 안전하지만 현재는 blog기반으로 작성 + // 블로그가 없을 때를 대비해서 Kakao 기본 설명을 fallback으로 변경 + List placeList = new ArrayList<>(); + for (int i = 0; i < flatKakao.size(); i++) { + KakaoPlaceResDTO k = flatKakao.get(i); + + // 대응되는 블로그가 없다면 간단한 설명을 생성(fallback) + String title = k.getPlaceName(); + String desc = Optional.ofNullable(k.getCategoryGroupName()).orElse("카테고리 정보 없음") + + " · " + Optional.ofNullable(k.getRoadAddressName()).orElse(k.getAddressName()); + + // 블로그 결과가 있다면 맨 앞 하나만 사용(원한다면 점수화/요약 로직 확장) + if (i < allBlogsFlat.size()) { + NaverBlogResDTO b = allBlogsFlat.get(i); + title = stripHtml(b.getTitle()); + desc = stripHtml(b.getDescription()); + } - // 3. AI API로 최종 추천 장소 조회 + placeList.add(AIReqDTO.builder() + .title(title) + .description(desc) + .build()); + } - // 3-1. 태그 이름 조회 - List tagNums = scheduleRegistReqDTO.getPlanRegistReqDTOList().stream() - .filter(Objects::nonNull) - .flatMap(p -> p.getTagList().stream()) - .collect(Collectors.toList()); - logger.info("#$# tagNums size={}, values={}", tagNums.size(), tagNums); + // ---------- 3) AI 호출 ---------- + List tagNums = Optional.ofNullable(plan.getTagList()).orElseGet(List::of); List tagNames = tagQueryService.findTagNmByTagNum(tagNums); - logger.info("#$# tagNames size={}, values={}", tagNames.size(), tagNames); - // 3-2. 네이버 블로그 결과를 title/description으로 변환 - List placeList = allNaverBlogResults.stream() - .flatMap(List::stream) - .map(b -> AIReqDTO.builder() - .title(stripHtml(b.getTitle())) - .description(stripHtml(b.getDescription())) - .build()) - .collect(Collectors.toList()); - logger.info("#$# placeList size={}, sample={}", placeList.size(), - placeList.stream().limit(3).collect(Collectors.toList())); - - // 3-3. AI 요청 래퍼 구성 AIReqWrapper aiReqWrapper = AIReqWrapper.builder() .tags(tagNames) .comment(Optional.ofNullable(scheduleRegistReqDTO.getComment()).orElse("")) .placeList(placeList) .build(); - logger.info("#$# AIReqWrapper ready: tags={}, comment.len={}, placeList.size={}", - aiReqWrapper.getTags(), - aiReqWrapper.getComment() == null ? 0 : aiReqWrapper.getComment().length(), - aiReqWrapper.getPlaceList().size()); - - // 4) AI 호출 AIResDTO aiRes = aiClient.recommandPlace(aiReqWrapper); logger.info("#$# AI Recommendation result obj={}", aiRes); - if (aiRes != null) { - logger.info("#$# AI result fields: recommandPlaceNum={}, aiDescription={}", - aiRes.getRecommandPlaceNum(), aiRes.getAiDescription()); - } + // ---------- 4) AI가 고른 index → Kakao place 선택 ---------- + Integer idx = (aiRes != null) ? aiRes.getRecommandPlaceIndex() : null; + if (idx == null || idx < 0 || idx >= flatKakao.size()) { + logger.warn("#$# invalid recommandPlaceNum={}, fallback to 0", idx); + idx = 0; // fallback + } + KakaoPlaceResDTO chosen = flatKakao.get(idx); + + // ---------- 5) DB insert (예시) ---------- + // Plan 엔티티는 프로젝트 엔티티에 맞게 매핑 필요 + // Plan planEntity = Plan.builder() + // .planNm(chosen.getPlaceName()) + // .planLink(chosen.getPlaceUrl()) + // .planDescription(aiRes != null ? aiRes.getAiDescription() : null) + // .planAddress(Optional.ofNullable(chosen.getRoadAddressName()).orElse(chosen.getAddressName())) + // .itemNum(plan.getItemNum()) + // .categoryNum(plan.getCategoryNum()) + // .startTime(plan.getStartTime()) + // .endTime(plan.getEndTime()) + // .build(); + // planRepository.save(planEntity); + + // ---------- 6) PlanRegistResDTO 구성 ---------- + PlanRegistResDTO planRes = PlanRegistResDTO.builder() + .planNum(null) // TODO. DB 저장 후 세팅 + .startTime(plan.getStartTime()) + .endTime(plan.getEndTime()) + .itemNum(plan.getItemNum()) + .itemNm(null) // TODO. itemNm(plan.getItemNm()) + .categoryNum(plan.getCategoryNum()) + .categoryNm(null)// TODO. categoryNm(plan.getCategoryNm()) + .planNm(chosen.getPlaceName()) + .planLink(chosen.getPlaceUrl()) + .planDescription(aiRes != null ? aiRes.getAiDescription() : null) + .planAddress(Optional.ofNullable(chosen.getRoadAddressName()).orElse(chosen.getAddressName())) + .regionName(null) // TODO. 지역 세팅 - firstRegionName(plan.getRegionRegistReqDTOList()) + .tagList(plan.getTagList()) + .build(); + planResList.add(planRes); } - logger.info("allNaverBlogResults: {}", allNaverBlogResults); - - - + // ---------- 7) ScheduleRegistResDTO 묶어서 리턴 ---------- + return ScheduleRegistResDTO.builder() + .scheduleNm(scheduleNm) + .startDate(startDate) + .endDate(endDate) + .planRegistResDTOList(planResList) + .build(); } + // 후보지역 수에 따라 각 지역의 후보장소 수를 리턴 // 후보장소 수만큼 네이버 블로그 API를 호출해야 하기 때문에 제한 필요 private int calLimitPlace(int regionCount) { @@ -157,4 +215,11 @@ private int calLimitPlace(int regionCount) { } return perRegionLimit; } + +// private String firstRegionName(List regions) { +// if (regions == null || regions.isEmpty()) return null; +// // RegionRegistReqDTO에 regionName 같은 필드가 있다면 그걸 반환 +// // 예시로 cityName+district 조합 등을 사용 +// return Optional.ofNullable(regions.get(0).getRegionName()).orElse(null); +// } } diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index b7a732b..dc2bc79 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -11,6 +11,7 @@ import com.barogagi.schedule.command.service.ScheduleCommandService; import com.barogagi.schedule.dto.ScheduleDetailResDTO; import com.barogagi.schedule.dto.ScheduleRegistReqDTO; +import com.barogagi.schedule.dto.ScheduleRegistResDTO; import com.barogagi.schedule.query.service.ScheduleQueryService; import com.barogagi.schedule.query.vo.ScheduleDetailVO; import com.barogagi.util.InputValidate; @@ -184,6 +185,7 @@ public ApiResponse registSchedule( String resultCode = ""; String message = ""; + ScheduleRegistResDTO scheduleRegistResDTO; try { //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ @@ -193,9 +195,8 @@ public ApiResponse registSchedule( resultCode = "101"; message = "입력값이 올바르지 않습니다."; } else { - scheduleCommandService.registSchedule(scheduleRegistReqDTO); - // ScheduleDetailResDTO result = scheduleQueryService.getScheduleDetail(scheduleNum); - // logger.info("result={}", result.toString()); + scheduleRegistResDTO = scheduleCommandService.registSchedule(scheduleRegistReqDTO); + apiResponse.setData(scheduleRegistResDTO); /*if (result == null) { resultCode = "300"; diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java new file mode 100644 index 0000000..9ba74c1 --- /dev/null +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java @@ -0,0 +1,23 @@ +package com.barogagi.schedule.dto; + +import com.barogagi.plan.dto.PlanRegistReqDTO; +import com.barogagi.plan.dto.PlanRegistResDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +@Builder(toBuilder = true) +@Schema(description = "일정 등록 응답 DTO") +public class ScheduleRegistResDTO { + private String scheduleNm; // 일정명 + private String startDate; // 시작 날짜 + private String endDate; // 종료 날짜 + + // 계획 리스트 + private List planRegistResDTOList; +} diff --git a/src/main/java/com/barogagi/util/HtmlUtils.java b/src/main/java/com/barogagi/util/HtmlUtils.java index 0112d8d..2f6d1da 100644 --- a/src/main/java/com/barogagi/util/HtmlUtils.java +++ b/src/main/java/com/barogagi/util/HtmlUtils.java @@ -19,4 +19,19 @@ public static String stripHtml(String s) { // 3) 공백 정리 return t.replaceAll("\\s+", " ").trim(); } + + + /** ```json ... ``` 형태 코드펜스/공백 제거 */ + public static String stripCodeFence(String s) { + if (s == null) return null; + // 앞쪽 ``` 또는 ```json, 뒤쪽 ``` 제거 + String t = s.trim(); + if (t.startsWith("```")) { + t = t.replaceFirst("^```(?:json)?\\s*", ""); + } + if (t.endsWith("```")) { + t = t.replaceFirst("```\\s*$", ""); + } + return t.trim(); + } } From 922a34592147e60a15342e8b45bc233c81b7cb58 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Tue, 26 Aug 2025 23:18:01 +0900 Subject: [PATCH 10/79] =?UTF-8?q?chore:=20=ED=95=84=EC=9A=94=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EA=B7=B8=20=EC=A3=BC=EC=84=9D=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/barogagi/ai/dto/AIResDTO.java | 5 +---- .../com/barogagi/kakaoplace/client/KakaoPlaceClient.java | 8 ++++---- .../com/barogagi/naverbolg/client/NaverBlogClient.java | 9 ++++----- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/barogagi/ai/dto/AIResDTO.java b/src/main/java/com/barogagi/ai/dto/AIResDTO.java index 99b1d63..506fc57 100644 --- a/src/main/java/com/barogagi/ai/dto/AIResDTO.java +++ b/src/main/java/com/barogagi/ai/dto/AIResDTO.java @@ -8,9 +8,6 @@ @AllArgsConstructor @Builder(toBuilder = true) public class AIResDTO { -// private String requestId; -// private String model; -// private String content; - private Integer recommandPlaceNum; + private Integer recommandPlaceIndex; private String aiDescription; } diff --git a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java index db387ff..8a473dd 100644 --- a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java +++ b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java @@ -35,11 +35,11 @@ public List searchKakaoPlace(String query, String x, String y, .queryParam("size", limitPlace) .build(false)); - logger.info("#$# url={}", url); +// logger.info("#$# url={}", url); HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "KakaoAK " + kakaoApiKey); - logger.info("#$# Request Headers: {}", headers); +// logger.info("#$# Request Headers: {}", headers); HttpEntity entity = new HttpEntity<>(headers); ResponseEntity response = restTemplate.exchange( @@ -48,8 +48,8 @@ public List searchKakaoPlace(String query, String x, String y, entity, KakaoPlaceSearchResDTO.class ); - logger.info("#$# response={}", response); - logger.info("#$# response.getDocuments={}", response.getBody().getDocuments()); +// logger.info("#$# response={}", response); +// logger.info("#$# response.getDocuments={}", response.getBody().getDocuments()); List body = response.getBody().getDocuments(); return body; diff --git a/src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java b/src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java index f36a2d5..0c492ba 100644 --- a/src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java +++ b/src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java @@ -37,13 +37,12 @@ public List searchNaverBlog(String query, int display) { .queryParam("query", query) .queryParam("display", display) .build(false)); - logger.info("#$# url={}", url); +// logger.info("#$# url={}", url); HttpHeaders headers = new HttpHeaders(); headers.set("X-Naver-Client-Id", xNaverClientId); headers.set("X-Naver-Client-Secret", xNaverClientSecret); - //headers.set("Content-Type", "application/json;charset=UTF-8 "); - logger.info("#$# Request Headers: {}", headers); +// logger.info("#$# Request Headers: {}", headers); HttpEntity entity = new HttpEntity<>(headers); ResponseEntity response = restTemplate.exchange( @@ -52,8 +51,8 @@ public List searchNaverBlog(String query, int display) { entity, NaverBlogSearchResDTO.class ); - logger.info("#$# response.getBody()={}", response.getBody()); - logger.info("#$# response.getBody().getItems()={}", response.getBody().getItems()); +// logger.info("#$# response.getBody()={}", response.getBody()); +// logger.info("#$# response.getBody().getItems()={}", response.getBody().getItems()); List body = response.getBody().getItems(); return body; From ec27791acf3b47a87f1e62a23bc9db0c72d885ff Mon Sep 17 00:00:00 2001 From: RohDamin Date: Tue, 26 Aug 2025 23:18:38 +0900 Subject: [PATCH 11/79] =?UTF-8?q?fix:=20tag=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=BF=BC=EB=A6=AC=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EB=AA=85=20?= =?UTF-8?q?=EB=8C=80=EB=AC=B8=EC=9E=90=EB=A1=9C=20=EC=88=98=EC=A0=95(?= =?UTF-8?q?=EC=86=8C=EB=AC=B8=EC=9E=90=20=EC=82=AC=EC=9A=A9=20=EC=8B=9C=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/mapper/TagMapper.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/mapper/TagMapper.xml b/src/main/resources/mapper/TagMapper.xml index 1fd84cd..46d4166 100644 --- a/src/main/resources/mapper/TagMapper.xml +++ b/src/main/resources/mapper/TagMapper.xml @@ -11,8 +11,8 @@ SELECT b.TAG_NUM as tagNum , b.TAG_NM as tagNm - FROM plan_tag a - JOIN tag b ON a.TAG_NUM = b.TAG_NUM + FROM PLAN_TAG a + JOIN TAG b ON a.TAG_NUM = b.TAG_NUM WHERE a.PLAN_NUM = #{planNum}; ]]> From 29f7a9c13a4207a5f56248fa45071503f6eaac03 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Tue, 26 Aug 2025 23:23:42 +0900 Subject: [PATCH 12/79] =?UTF-8?q?chore:=20AIClient=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/barogagi/ai/client/AIClient.java | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/src/main/java/com/barogagi/ai/client/AIClient.java b/src/main/java/com/barogagi/ai/client/AIClient.java index 5ef0168..0ab4b0b 100644 --- a/src/main/java/com/barogagi/ai/client/AIClient.java +++ b/src/main/java/com/barogagi/ai/client/AIClient.java @@ -80,43 +80,6 @@ public AIResDTO recommandPlace(AIReqWrapper aiReqWrapper) { } } -// String url = aiBaseUrl + aiPath; -// -// HttpHeaders headers = new HttpHeaders(); -// headers.setBearerAuth(aiApiKey); -// headers.setContentType(MediaType.APPLICATION_JSON); -// headers.setAccept(List.of(MediaType.APPLICATION_JSON)); -// -// ChatRequest chatRequest = buildChatRequest(aiReqWrapper); -// HttpEntity entity = new HttpEntity<>(chatRequest, headers); -// -// // --- 최소 요청 로그 --- -// logger.info("#$# AI url={}", url); -// logger.info("#$# AI req: tags={}, commentLen={}, placeList={}", -// aiReqWrapper.getTags() == null ? 0 : aiReqWrapper.getTags().size(), -// aiReqWrapper.getComment() == null ? 0 : aiReqWrapper.getComment().length(), -// aiReqWrapper.getPlaceList() == null ? 0 : aiReqWrapper.getPlaceList().size()); -// -// // --- 응답 원문(String)으로 1회 호출 → 로그 찍고 → DTO로 매핑 --- -// ResponseEntity resp = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); -// logger.info("#$# AI resp status={}", resp.getStatusCode()); -// String raw = resp.getBody(); -// logger.info("#$# AI resp raw (preview): {}", raw == null -// ? "null" -// : (raw.length() <= 1000 ? raw : raw.substring(0, 1000) + "...(truncated)")); -// -// // --- DTO 매핑 (실패해도 예외만 로그) --- -// try { -// AIResDTO dto = OM.readValue(raw, AIResDTO.class); -// logger.info("#$# AI resp DTO: recommandPlaceNum={}, aiDescription='{}'", -// dto.getRecommandPlaceNum(), dto.getAiDescription()); -// return dto; -// } catch (Exception e) { -// logger.warn("#$# parse to AIResDTO failed: {}", e.getMessage()); -// return null; -// } -// } - // ai에게 요청 가능한 형태로 변경 private ChatRequest buildChatRequest(AIReqWrapper wrapper) { // system 메시지 From cfb71f1a492775bf493d27b72618424b60bc47eb Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sat, 20 Sep 2025 18:41:22 +0900 Subject: [PATCH 13/79] =?UTF-8?q?feat:=20=EC=A3=BC=EC=86=8C=EB=A5=BC=20x,y?= =?UTF-8?q?=20=EC=A2=8C=ED=91=9C=EB=A1=9C=20=EB=B3=80=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8A=94=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kakaoplace/client/KakaoGeoCodeClient.java | 49 +++++++++++ .../kakaoplace/client/KakaoPlaceClient.java | 4 - .../kakaoplace/dto/KakaoGeoCodeResDTO.java | 19 ++++ .../dto/KakaoGeoCodeSearchResDTO.java | 18 ++++ .../region/controller/RegionController.java | 87 +++++++++++++++++++ .../region/dto/RegionGeoCodeResDTO.java | 17 ++++ .../region/query/mapper/RegionMapper.java | 3 + .../query/service/RegionGeoCodeService.java | 80 +++++++++++++++++ src/main/resources/mapper/RegionMapper.xml | 15 ++++ 9 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/barogagi/kakaoplace/client/KakaoGeoCodeClient.java create mode 100644 src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeResDTO.java create mode 100644 src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeSearchResDTO.java create mode 100644 src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java create mode 100644 src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java diff --git a/src/main/java/com/barogagi/kakaoplace/client/KakaoGeoCodeClient.java b/src/main/java/com/barogagi/kakaoplace/client/KakaoGeoCodeClient.java new file mode 100644 index 0000000..611e604 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/client/KakaoGeoCodeClient.java @@ -0,0 +1,49 @@ +package com.barogagi.kakaoplace.client; + +import com.barogagi.kakaoplace.dto.KakaoGeoCodeResDTO; +import com.barogagi.kakaoplace.dto.KakaoGeoCodeSearchResDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; + +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.List; + +@Service +public class KakaoGeoCodeClient { + private static final Logger logger = LoggerFactory.getLogger(KakaoGeoCodeClient.class); + + @Value("${kakao.rest-api-key}") + private String kakaoApiKey; + + private final RestTemplate restTemplate = new RestTemplate(); + + public List convertKakaoGeoCode(String address) { + String url = String.valueOf(UriComponentsBuilder +// .fromHttpUrl("https://dapi.kakao.com/v2/local/search/keyword.json") + .fromHttpUrl("https://dapi.kakao.com/v2/local/search/address.json") + .queryParam("query", address) + .build(false)); + + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "KakaoAK " + kakaoApiKey); + + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + KakaoGeoCodeSearchResDTO.class + ); + + List body = response.getBody().getDocuments(); + return body; + } +} diff --git a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java index 8a473dd..929b3cf 100644 --- a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java +++ b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java @@ -35,11 +35,9 @@ public List searchKakaoPlace(String query, String x, String y, .queryParam("size", limitPlace) .build(false)); -// logger.info("#$# url={}", url); HttpHeaders headers = new HttpHeaders(); headers.set("Authorization", "KakaoAK " + kakaoApiKey); -// logger.info("#$# Request Headers: {}", headers); HttpEntity entity = new HttpEntity<>(headers); ResponseEntity response = restTemplate.exchange( @@ -48,8 +46,6 @@ public List searchKakaoPlace(String query, String x, String y, entity, KakaoPlaceSearchResDTO.class ); -// logger.info("#$# response={}", response); -// logger.info("#$# response.getDocuments={}", response.getBody().getDocuments()); List body = response.getBody().getDocuments(); return body; diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeResDTO.java new file mode 100644 index 0000000..d05a996 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeResDTO.java @@ -0,0 +1,19 @@ +package com.barogagi.kakaoplace.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "카카오 주소로 좌표 변환 DTO") +public class KakaoGeoCodeResDTO { + @JsonProperty("x") + private String x; + + @JsonProperty("y") + private String y; +} diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeSearchResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeSearchResDTO.java new file mode 100644 index 0000000..33b7245 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeSearchResDTO.java @@ -0,0 +1,18 @@ +package com.barogagi.kakaoplace.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "카카오 주소로 좌표 변환 결과 DTO") +public class KakaoGeoCodeSearchResDTO { + @JsonProperty("documents") + private List documents; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/region/controller/RegionController.java b/src/main/java/com/barogagi/region/controller/RegionController.java index 205afb4..ebbd669 100644 --- a/src/main/java/com/barogagi/region/controller/RegionController.java +++ b/src/main/java/com/barogagi/region/controller/RegionController.java @@ -1,9 +1,96 @@ package com.barogagi.region.controller; +import com.barogagi.plan.query.service.PlanQueryService; +import com.barogagi.region.dto.RegionGeoCodeResDTO; +import com.barogagi.region.query.service.RegionGeoCodeService; +import com.barogagi.region.query.service.RegionQueryService; +import com.barogagi.response.ApiResponse; +import com.barogagi.schedule.command.service.ScheduleCommandService; +import com.barogagi.schedule.controller.ScheduleController; +import com.barogagi.schedule.dto.ScheduleDetailResDTO; +import com.barogagi.schedule.query.service.ScheduleQueryService; +import com.barogagi.util.InputValidate; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +@Tag(name = "지역", description = "지역 관련 API") @RestController @RequestMapping("/region") public class RegionController { + private static final Logger logger = LoggerFactory.getLogger(RegionController.class); + + private final InputValidate inputValidate; + + private final RegionGeoCodeService regionGeoCodeService; + + private final String API_SECRET_KEY; + + public RegionController(Environment environment, + InputValidate inputValidate, + RegionGeoCodeService regionGeoCodeService) { + this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + this.inputValidate = inputValidate; + this.regionGeoCodeService = regionGeoCodeService; + } + @Operation(summary = "주소를 x,y 좌표로 변환하는 기능", description = "법정동/행정동 주소를 받아서 x,y 좌표로 변환하는 기능입니다") + @GetMapping("/geocode") + public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 주소 번호", example = "1") @RequestParam Integer regionNum) { + + logger.info("CALL /region/geocode"); + logger.info("[input] SchedulNm={}", regionNum); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ + if (true) { + + // TODO. 에러 메시지 정의하기 + if (inputValidate.isInvalidInteger(regionNum)) { + resultCode = "101"; + message = "좌표로 변환할 법정동/행정동 번호가 숫자가 아닙니다."; + } else { + + RegionGeoCodeResDTO result = regionGeoCodeService.getGeocode(regionNum); + logger.info("result={}", result.toString()); + + if (result == null) { + resultCode = "300"; + message = "조회할 일정이 존재하지 않습니다."; // TODO. 에러 메시지 정의하기 + + } else { + resultCode = "200"; + message = "일정 상세 조회 성공"; + apiResponse.setData(result); + logger.info("#$# result={}", result.toString()); + } + } + + } else { + resultCode = "100"; + message = "잘못된 접근입니다."; + } + logger.info("#$# 11 resultCode={}", resultCode); + } catch (Exception e) { + resultCode = "400"; + message = "오류가 발생하였습니다."; + throw new RuntimeException(e); + + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); + } + return apiResponse; + } } diff --git a/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java b/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java new file mode 100644 index 0000000..2477717 --- /dev/null +++ b/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java @@ -0,0 +1,17 @@ +package com.barogagi.region.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder(toBuilder = true) +@ToString +public class RegionGeoCodeResDTO { + @Schema(description = "x 좌표", example = "127.04892851392") + public String x; + + @Schema(description = "y 좌표", example = "37.5091105328378") + public String y; +} diff --git a/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java b/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java index 0f70e5c..ed05a60 100644 --- a/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java +++ b/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java @@ -10,4 +10,7 @@ public interface RegionMapper { // 계획 상세 조회 - 지역 상세 조회 List selectRegionByPlanNum(int planNum); + + // 지역 번호로 지역명 조회 + RegionDetailVO selectRegionByRegionNum(int regionNum); } diff --git a/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java b/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java new file mode 100644 index 0000000..170cf7c --- /dev/null +++ b/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java @@ -0,0 +1,80 @@ +package com.barogagi.region.query.service; + +import com.barogagi.ai.client.AIClient; +import com.barogagi.kakaoplace.client.KakaoGeoCodeClient; +import com.barogagi.kakaoplace.client.KakaoPlaceClient; +import com.barogagi.kakaoplace.dto.KakaoGeoCodeResDTO; +import com.barogagi.naverbolg.client.NaverBlogClient; +import com.barogagi.plan.query.mapper.CategoryMapper; +import com.barogagi.region.controller.RegionController; +import com.barogagi.region.dto.RegionGeoCodeResDTO; +import com.barogagi.region.query.mapper.RegionMapper; +import com.barogagi.region.query.vo.RegionDetailVO; +import com.barogagi.tag.query.service.TagQueryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Service +public class RegionGeoCodeService { + private static final Logger logger = LoggerFactory.getLogger(RegionGeoCodeService.class); + + private final KakaoGeoCodeClient kakaoGeoCodeClient; + + private final RegionMapper regionMapper; + + @Autowired + public RegionGeoCodeService(KakaoGeoCodeClient kakaoGeoCodeClient, + RegionMapper regionMapper) { + this.kakaoGeoCodeClient = kakaoGeoCodeClient; + this.regionMapper = regionMapper; + } + + /** + * 주소를 받아서 Kakao API로 좌표(x, y) 변환 + */ + public RegionGeoCodeResDTO getGeocode(Integer regionNum) { + + RegionDetailVO region = regionMapper.selectRegionByRegionNum(regionNum); + + if (region == null) { // todo. 에러 처리 필요 + return null; + } + + // 2. address 문자열 조립 (null/빈 값 무시) + String address = Stream.of( + region.getRegionLevel1(), + region.getRegionLevel2(), + region.getRegionLevel3(), + region.getRegionLevel4() + ) + .filter(Objects::nonNull) + .filter(s -> !s.isBlank()) + .collect(Collectors.joining(" ")); + + logger.info("address = {}", address); + + + // 3. 카카오 API 호출 + List result = kakaoGeoCodeClient.convertKakaoGeoCode(address); + + if (result.isEmpty()) { + return null; + } + + // 4. documents 배열 중 첫 번째 좌표를 DTO에 담아서 리턴 + KakaoGeoCodeResDTO first = result.get(0); + + return RegionGeoCodeResDTO.builder() + .x(first.getX()) + .y(first.getY()) + .build(); + } +} + diff --git a/src/main/resources/mapper/RegionMapper.xml b/src/main/resources/mapper/RegionMapper.xml index eef2454..f984ae7 100644 --- a/src/main/resources/mapper/RegionMapper.xml +++ b/src/main/resources/mapper/RegionMapper.xml @@ -21,4 +21,19 @@ WHERE a.PLAN_NUM = #{planNum}; ]]> + + + + From dfe9f5cc2e95afd0acd7a7ae4aac45941d73dcec Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sat, 20 Sep 2025 19:33:26 +0900 Subject: [PATCH 14/79] =?UTF-8?q?feat:=20ApiResponse=EC=97=90=20success/er?= =?UTF-8?q?ror=20=EC=A0=95=EC=A0=81=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/barogagi/response/ApiResponse.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/com/barogagi/response/ApiResponse.java b/src/main/java/com/barogagi/response/ApiResponse.java index 5568d6b..cb261b9 100644 --- a/src/main/java/com/barogagi/response/ApiResponse.java +++ b/src/main/java/com/barogagi/response/ApiResponse.java @@ -15,4 +15,19 @@ public class ApiResponse { // 데이터 private T data; + + public static ApiResponse success(T data, String message) { + ApiResponse res = new ApiResponse<>(); + res.resultCode = "200"; + res.message = message; + res.data = data; + return res; + } + + public static ApiResponse error(String code, String message) { + ApiResponse res = new ApiResponse<>(); + res.resultCode = code; + res.message = message; + return res; + } } From 4065e213fa2ec4e300d5ed91ba9cd32229daa7e0 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sat, 20 Sep 2025 19:34:06 +0900 Subject: [PATCH 15/79] =?UTF-8?q?feat:=20=EC=A3=BC=EC=86=8C=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EA=B2=80=EC=83=89=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../region/controller/RegionController.java | 31 +++++-- .../region/dto/RegionSearchResDTO.java | 14 +++ .../region/query/mapper/RegionMapper.java | 4 + .../query/service/RegionQueryService.java | 86 +++++++++++++++++++ src/main/resources/mapper/RegionMapper.xml | 28 ++++++ 5 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/barogagi/region/dto/RegionSearchResDTO.java diff --git a/src/main/java/com/barogagi/region/controller/RegionController.java b/src/main/java/com/barogagi/region/controller/RegionController.java index ebbd669..a231cff 100644 --- a/src/main/java/com/barogagi/region/controller/RegionController.java +++ b/src/main/java/com/barogagi/region/controller/RegionController.java @@ -2,6 +2,7 @@ import com.barogagi.plan.query.service.PlanQueryService; import com.barogagi.region.dto.RegionGeoCodeResDTO; +import com.barogagi.region.dto.RegionSearchResDTO; import com.barogagi.region.query.service.RegionGeoCodeService; import com.barogagi.region.query.service.RegionQueryService; import com.barogagi.response.ApiResponse; @@ -21,6 +22,8 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @Tag(name = "지역", description = "지역 관련 API") @RestController @RequestMapping("/region") @@ -31,21 +34,25 @@ public class RegionController { private final RegionGeoCodeService regionGeoCodeService; + private final RegionQueryService regionQueryService; + private final String API_SECRET_KEY; public RegionController(Environment environment, InputValidate inputValidate, - RegionGeoCodeService regionGeoCodeService) { + RegionGeoCodeService regionGeoCodeService, + RegionQueryService regionQueryService) { this.API_SECRET_KEY = environment.getProperty("api.secret-key"); this.inputValidate = inputValidate; this.regionGeoCodeService = regionGeoCodeService; + this.regionQueryService = regionQueryService; } @Operation(summary = "주소를 x,y 좌표로 변환하는 기능", description = "법정동/행정동 주소를 받아서 x,y 좌표로 변환하는 기능입니다") @GetMapping("/geocode") public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 주소 번호", example = "1") @RequestParam Integer regionNum) { logger.info("CALL /region/geocode"); - logger.info("[input] SchedulNm={}", regionNum); + logger.info("[input] regionNum={}", regionNum); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -67,13 +74,12 @@ public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 if (result == null) { resultCode = "300"; - message = "조회할 일정이 존재하지 않습니다."; // TODO. 에러 메시지 정의하기 + message = "조회할 지역이 존재하지 않습니다."; // TODO. 에러 메시지 정의하기 } else { resultCode = "200"; - message = "일정 상세 조회 성공"; + message = "주소 좌표 변환 성공"; apiResponse.setData(result); - logger.info("#$# result={}", result.toString()); } } @@ -81,7 +87,6 @@ public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 resultCode = "100"; message = "잘못된 접근입니다."; } - logger.info("#$# 11 resultCode={}", resultCode); } catch (Exception e) { resultCode = "400"; message = "오류가 발생하였습니다."; @@ -93,4 +98,18 @@ public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 } return apiResponse; } + + @Operation(summary = "주소 목록을 검색하는 기능", description = "주소 목록을 검색하는 기능입니다.
" + + "REGION table에 저장된 값 중, level 1부터 4까지 가장 정확도 높은 순으로 지역명 최대 10개를 리턴합니다.
" + + "행정구역 단계(시/도, 시/군/구, 동/면/리)를 조합하여 결과를 반환하며, 중복되는 상위 주소(예: '서울특별시 강남구')는 한 번만 표시됩니다.") + @GetMapping("/searchList") + public ApiResponse searchList(@Parameter(description = "검색할 주소명", example = "강남") @RequestParam String regionQuery) { + + logger.info("CALL /region/searchList"); + logger.info("[input] regionQuery={}", regionQuery); + + List result = regionQueryService.searchList(regionQuery); + return ApiResponse.success(result, "주소 목록 검색 성공"); + + } } diff --git a/src/main/java/com/barogagi/region/dto/RegionSearchResDTO.java b/src/main/java/com/barogagi/region/dto/RegionSearchResDTO.java new file mode 100644 index 0000000..1e61e5f --- /dev/null +++ b/src/main/java/com/barogagi/region/dto/RegionSearchResDTO.java @@ -0,0 +1,14 @@ +package com.barogagi.region.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder(toBuilder = true) +@ToString +public class RegionSearchResDTO { + public String regionNm; + public Integer regionNum; +} diff --git a/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java b/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java index ed05a60..638e3ee 100644 --- a/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java +++ b/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java @@ -13,4 +13,8 @@ public interface RegionMapper { // 지역 번호로 지역명 조회 RegionDetailVO selectRegionByRegionNum(int regionNum); + + // 키워드로 지역 목록 검색 - level 1부터 가장 정확도 높은 순으로 10개 리턴 + List selectRegionByRegionNm(String regionQuery); + } diff --git a/src/main/java/com/barogagi/region/query/service/RegionQueryService.java b/src/main/java/com/barogagi/region/query/service/RegionQueryService.java index 40f4903..60c22be 100644 --- a/src/main/java/com/barogagi/region/query/service/RegionQueryService.java +++ b/src/main/java/com/barogagi/region/query/service/RegionQueryService.java @@ -1,7 +1,93 @@ package com.barogagi.region.query.service; +import com.barogagi.kakaoplace.client.KakaoGeoCodeClient; +import com.barogagi.kakaoplace.dto.KakaoGeoCodeResDTO; +import com.barogagi.region.dto.RegionGeoCodeResDTO; +import com.barogagi.region.dto.RegionSearchResDTO; +import com.barogagi.region.query.mapper.RegionMapper; +import com.barogagi.region.query.vo.RegionDetailVO; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + @Service public class RegionQueryService { + + private final RegionMapper regionMapper; + + @Autowired + public RegionQueryService(RegionMapper regionMapper) { + this.regionMapper = regionMapper; + } + + + /** + * 검색어로 지역 정보를 조회하고, + * 각 지역의 행정구역 단계별 주소를 RegionSearchResDTO 리스트로 반환한다. + * + * 예시: + * - CASE 1: 서울특별시 / null / 강남구 / 역삼동 + * → "서울특별시 강남구" + * → "서울특별시 강남구 역삼동" + * + * - CASE 2: 경기도 / 안양시 / 만안구 / 석수동 + * → "경기도 안양시" + * → "경기도 안양시 만안구" + * → "경기도 안양시 만안구 석수동" + * + * - CASE 3: 울산광역시 / 울주군 / null / 온산읍 + * → "울산광역시 울주군" + * → "울산광역시 울주군 온산읍" + * + * @param regionQuery 검색 키워드 + * @return RegionSearchResDTO 리스트 (단계별 주소 포함) + */ + public List searchList(String regionQuery) { + List regionList = regionMapper.selectRegionByRegionNm(regionQuery); + + List result = new ArrayList<>(); + Set seen = new HashSet<>(); // 중복 방지 + + for (RegionDetailVO r : regionList) { + List parts = new ArrayList<>(); + + if (r.getRegionLevel1() != null && !r.getRegionLevel1().isBlank()) { + parts.add(r.getRegionLevel1()); + } + if (r.getRegionLevel2() != null && !r.getRegionLevel2().isBlank()) { + parts.add(r.getRegionLevel2()); + } + if (r.getRegionLevel3() != null && !r.getRegionLevel3().isBlank()) { + parts.add(r.getRegionLevel3()); + } + + // 상위 주소 (레벨1~레벨3까지) → 중복 방지 후 추가 + String upperAddress = String.join(" ", parts); + if (!upperAddress.isBlank() && seen.add(upperAddress)) { + result.add(RegionSearchResDTO.builder() + .regionNum(r.getRegionNum()) + .regionNm(upperAddress) + .build()); + } + + // 레벨4 (동/면/리) + if (r.getRegionLevel4() != null && !r.getRegionLevel4().isBlank()) { + String fullAddress = upperAddress + " " + r.getRegionLevel4(); + if (seen.add(fullAddress)) { + result.add(RegionSearchResDTO.builder() + .regionNum(r.getRegionNum()) + .regionNm(fullAddress) + .build()); + } + } + } + + return result; + } + + } + diff --git a/src/main/resources/mapper/RegionMapper.xml b/src/main/resources/mapper/RegionMapper.xml index f984ae7..d136c34 100644 --- a/src/main/resources/mapper/RegionMapper.xml +++ b/src/main/resources/mapper/RegionMapper.xml @@ -35,5 +35,33 @@ ]]> + + + From dc98ee1a4c21308bbc87b0184fa369207216769f Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sat, 20 Sep 2025 20:11:53 +0900 Subject: [PATCH 16/79] =?UTF-8?q?fix:=20naverBlog=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{naverbolg => naverblog}/client/NaverBlogClient.java | 9 +++------ .../{naverbolg => naverblog}/dto/NaverBlogResDTO.java | 4 +--- .../dto/NaverBlogSearchResDTO.java | 3 +-- .../com/barogagi/region/controller/RegionController.java | 2 +- .../region/query/service/RegionGeoCodeService.java | 6 ------ .../schedule/command/service/ScheduleCommandService.java | 6 ++---- 6 files changed, 8 insertions(+), 22 deletions(-) rename src/main/java/com/barogagi/{naverbolg => naverblog}/client/NaverBlogClient.java (86%) rename src/main/java/com/barogagi/{naverbolg => naverblog}/dto/NaverBlogResDTO.java (92%) rename src/main/java/com/barogagi/{naverbolg => naverblog}/dto/NaverBlogSearchResDTO.java (88%) diff --git a/src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java b/src/main/java/com/barogagi/naverblog/client/NaverBlogClient.java similarity index 86% rename from src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java rename to src/main/java/com/barogagi/naverblog/client/NaverBlogClient.java index 0c492ba..5975a87 100644 --- a/src/main/java/com/barogagi/naverbolg/client/NaverBlogClient.java +++ b/src/main/java/com/barogagi/naverblog/client/NaverBlogClient.java @@ -1,10 +1,7 @@ -package com.barogagi.naverbolg.client; +package com.barogagi.naverblog.client; -import com.barogagi.kakaoplace.client.KakaoPlaceClient; -import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; -import com.barogagi.kakaoplace.dto.KakaoPlaceSearchResDTO; -import com.barogagi.naverbolg.dto.NaverBlogResDTO; -import com.barogagi.naverbolg.dto.NaverBlogSearchResDTO; +import com.barogagi.naverblog.dto.NaverBlogResDTO; +import com.barogagi.naverblog.dto.NaverBlogSearchResDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; diff --git a/src/main/java/com/barogagi/naverbolg/dto/NaverBlogResDTO.java b/src/main/java/com/barogagi/naverblog/dto/NaverBlogResDTO.java similarity index 92% rename from src/main/java/com/barogagi/naverbolg/dto/NaverBlogResDTO.java rename to src/main/java/com/barogagi/naverblog/dto/NaverBlogResDTO.java index e24be91..7a45e83 100644 --- a/src/main/java/com/barogagi/naverbolg/dto/NaverBlogResDTO.java +++ b/src/main/java/com/barogagi/naverblog/dto/NaverBlogResDTO.java @@ -1,11 +1,9 @@ -package com.barogagi.naverbolg.dto; +package com.barogagi.naverblog.dto; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import java.util.List; - @Data @Getter @ToString diff --git a/src/main/java/com/barogagi/naverbolg/dto/NaverBlogSearchResDTO.java b/src/main/java/com/barogagi/naverblog/dto/NaverBlogSearchResDTO.java similarity index 88% rename from src/main/java/com/barogagi/naverbolg/dto/NaverBlogSearchResDTO.java rename to src/main/java/com/barogagi/naverblog/dto/NaverBlogSearchResDTO.java index aefa8e8..6037548 100644 --- a/src/main/java/com/barogagi/naverbolg/dto/NaverBlogSearchResDTO.java +++ b/src/main/java/com/barogagi/naverblog/dto/NaverBlogSearchResDTO.java @@ -1,6 +1,5 @@ -package com.barogagi.naverbolg.dto; +package com.barogagi.naverblog.dto; -import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; diff --git a/src/main/java/com/barogagi/region/controller/RegionController.java b/src/main/java/com/barogagi/region/controller/RegionController.java index a231cff..8a2312e 100644 --- a/src/main/java/com/barogagi/region/controller/RegionController.java +++ b/src/main/java/com/barogagi/region/controller/RegionController.java @@ -47,7 +47,7 @@ public RegionController(Environment environment, this.regionGeoCodeService = regionGeoCodeService; this.regionQueryService = regionQueryService; } - @Operation(summary = "주소를 x,y 좌표로 변환하는 기능", description = "법정동/행정동 주소를 받아서 x,y 좌표로 변환하는 기능입니다") + @Operation(summary = "주소를 x,y 좌표로 변환하는 기능", description = "주소 번호를 받아서 x,y 좌표로 변환하는 기능입니다") @GetMapping("/geocode") public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 주소 번호", example = "1") @RequestParam Integer regionNum) { diff --git a/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java b/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java index 170cf7c..b540913 100644 --- a/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java +++ b/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java @@ -1,16 +1,10 @@ package com.barogagi.region.query.service; -import com.barogagi.ai.client.AIClient; import com.barogagi.kakaoplace.client.KakaoGeoCodeClient; -import com.barogagi.kakaoplace.client.KakaoPlaceClient; import com.barogagi.kakaoplace.dto.KakaoGeoCodeResDTO; -import com.barogagi.naverbolg.client.NaverBlogClient; -import com.barogagi.plan.query.mapper.CategoryMapper; -import com.barogagi.region.controller.RegionController; import com.barogagi.region.dto.RegionGeoCodeResDTO; import com.barogagi.region.query.mapper.RegionMapper; import com.barogagi.region.query.vo.RegionDetailVO; -import com.barogagi.tag.query.service.TagQueryService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index e311917..226d20f 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -6,8 +6,8 @@ import com.barogagi.ai.dto.AIResDTO; import com.barogagi.kakaoplace.client.KakaoPlaceClient; import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; -import com.barogagi.naverbolg.client.NaverBlogClient; -import com.barogagi.naverbolg.dto.NaverBlogResDTO; +import com.barogagi.naverblog.client.NaverBlogClient; +import com.barogagi.naverblog.dto.NaverBlogResDTO; import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.dto.PlanRegistResDTO; import com.barogagi.plan.query.mapper.CategoryMapper; @@ -22,13 +22,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -import java.util.regex.Pattern; import static com.barogagi.util.HtmlUtils.stripHtml; From 20da55df3df294be527844e2dc57118c720eaae0 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Mon, 6 Oct 2025 13:54:42 +0900 Subject: [PATCH 17/79] =?UTF-8?q?fix:=20tagList=EB=A5=BC=20int=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=EC=9D=B4=20=EC=95=84=EB=8B=88=EB=9D=BC=20map=20?= =?UTF-8?q?=ED=98=95=ED=83=9C=EB=A1=9C=20=EB=B3=B4=EB=82=B4=EC=A3=BC?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../barogagi/plan/dto/PlanRegistReqDTO.java | 2 +- .../barogagi/plan/dto/PlanRegistResDTO.java | 4 +- .../region/dto/RegionGeoCodeResDTO.java | 10 ++ .../region/dto/RegionRegistReqDTO.java | 30 ++--- .../query/service/RegionGeoCodeService.java | 5 + .../service/ScheduleCommandService.java | 111 +++++++++++++++--- .../controller/ScheduleController.java | 84 +++++-------- .../schedule/dto/ScheduleRegistReqDTO.java | 4 - .../tag/controller/TagController.java | 54 +++++++++ .../com/barogagi/tag/dto/TagRegistResDTO.java | 18 +++ .../com/barogagi/tag/dto/TagSearchReqDTO.java | 14 +++ .../com/barogagi/tag/dto/TagSearchResDTO.java | 16 +++ .../java/com/barogagi/tag/enums/TagType.java | 31 +++++ .../barogagi/tag/query/mapper/TagMapper.java | 5 + .../tag/query/service/TagQueryService.java | 11 ++ .../barogagi/tag/query/vo/TagDetailVO.java | 3 + src/main/resources/mapper/TagMapper.xml | 30 ++++- 17 files changed, 338 insertions(+), 94 deletions(-) create mode 100644 src/main/java/com/barogagi/tag/controller/TagController.java create mode 100644 src/main/java/com/barogagi/tag/dto/TagRegistResDTO.java create mode 100644 src/main/java/com/barogagi/tag/dto/TagSearchReqDTO.java create mode 100644 src/main/java/com/barogagi/tag/dto/TagSearchResDTO.java create mode 100644 src/main/java/com/barogagi/tag/enums/TagType.java diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java index adba183..d378a5c 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java @@ -34,6 +34,6 @@ public class PlanRegistReqDTO { public List regionRegistReqDTOList; @Schema(description = "태그 목록") - public List tagList; + public List tagRegistReqDTOList; } diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java index 485f314..f05ba2e 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java @@ -1,6 +1,8 @@ package com.barogagi.plan.dto; import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.tag.dto.TagRegistReqDTO; +import com.barogagi.tag.dto.TagRegistResDTO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -51,6 +53,6 @@ public class PlanRegistResDTO { public String regionName; @Schema(description = "태그 목록") - public List tagList; + public List tagRegistResDTOList; } diff --git a/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java b/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java index 2477717..d80660f 100644 --- a/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java +++ b/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java @@ -14,4 +14,14 @@ public class RegionGeoCodeResDTO { @Schema(description = "y 좌표", example = "37.5091105328378") public String y; + + public int regionNum; + + public String regionLevel1; + + public String regionLevel2; + + public String regionLevel3; + + public String regionLevel4; } diff --git a/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java b/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java index 73c5311..9225d41 100644 --- a/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java +++ b/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java @@ -1,30 +1,30 @@ package com.barogagi.region.dto; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; import lombok.Getter; import lombok.ToString; @Getter @ToString @Schema(description = "지역 정보 DTO") +@Builder(toBuilder = true) public class RegionRegistReqDTO { - // 카카오 api에서 지역 데이터 어떻게 넘겨주는지 확인 필요 +// @Schema(description = "지역명 대분류", example = "서울특별시") +// public String regionNm1; - @Schema(description = "지역명 대분류", example = "서울특별시") - public String regionNm1; +// @Schema(description = "지역명 소분류", example = "강남구") +// public String regionNm2; - @Schema(description = "지역명 소분류", example = "강남구") - public String regionNm2; +// @Schema(description = "x 좌표", example = "127.04892851392") +// public String x; - @Schema(description = "x 좌표", example = "127.04892851392") - public String x; +// @Schema(description = "y 좌표", example = "37.5091105328378") +// public String y; - @Schema(description = "y 좌표", example = "37.5091105328378") - public String y; - -// public int regionNum; // 지역 번호 -// public String regionLevel1; // 대분류 -// public String regionLevel2; // 시/군 -// public String regionLevel3; // 구 -// public String regionLevel4; // 동/면/리 + public int regionNum; // 지역 번호 + public String regionLevel1; // 대분류 + public String regionLevel2; // 시/군 + public String regionLevel3; // 구 + public String regionLevel4; // 동/면/리 } diff --git a/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java b/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java index b540913..51f255f 100644 --- a/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java +++ b/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java @@ -68,6 +68,11 @@ public RegionGeoCodeResDTO getGeocode(Integer regionNum) { return RegionGeoCodeResDTO.builder() .x(first.getX()) .y(first.getY()) + .regionNum(region.getRegionNum()) + .regionLevel1(region.getRegionLevel1()) + .regionLevel2(region.getRegionLevel2()) + .regionLevel3(region.getRegionLevel3()) + .regionLevel4(region.getRegionLevel4()) .build(); } } diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 226d20f..81c9dde 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -11,9 +11,14 @@ import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.dto.PlanRegistResDTO; import com.barogagi.plan.query.mapper.CategoryMapper; +import com.barogagi.region.dto.RegionGeoCodeResDTO; import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.region.query.service.RegionGeoCodeService; +import com.barogagi.region.query.service.RegionQueryService; import com.barogagi.schedule.dto.ScheduleRegistReqDTO; import com.barogagi.schedule.dto.ScheduleRegistResDTO; +import com.barogagi.tag.dto.TagRegistReqDTO; +import com.barogagi.tag.dto.TagRegistResDTO; import com.barogagi.tag.query.service.TagQueryService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,10 +27,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; import static com.barogagi.util.HtmlUtils.stripHtml; @@ -40,8 +42,11 @@ public class ScheduleCommandService { private final AIClient aiClient; private final TagQueryService tagQueryService; + private final RegionGeoCodeService regionGeoCodeService; + @Value("${kakao.radius}") + private int radius; @Value("${naver.display}") private int naverBlogDisplay; @@ -49,12 +54,13 @@ public class ScheduleCommandService { @Autowired public ScheduleCommandService(CategoryMapper categoryMapper, KakaoPlaceClient kakaoPlaceClient, NaverBlogClient naverBlogClient, - AIClient aiClient, TagQueryService tagQueryService) { + AIClient aiClient, TagQueryService tagQueryService, RegionGeoCodeService regionGeoCodeService) { this.categoryMapper = categoryMapper; this.kakaoPlaceClient = kakaoPlaceClient; this.naverBlogClient = naverBlogClient; this.aiClient = aiClient; this.tagQueryService = tagQueryService; + this.regionGeoCodeService = regionGeoCodeService; } @Transactional @@ -67,27 +73,71 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe String startDate = scheduleRegistReqDTO.getStartDate(); String endDate = scheduleRegistReqDTO.getEndDate(); - for (PlanRegistReqDTO plan : scheduleRegistReqDTO.getPlanRegistReqDTOList()) { - int radius = scheduleRegistReqDTO.getRadius(); - // ---------- 1) Kakao 후보장소 수집(평탄화) ---------- - List> allKakaoPlaceResults = new ArrayList<>(); - String queryString = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); // 검색어 + scheduleRegistReqDTO.getPlanRegistReqDTOList().forEach(plan -> { + if (plan.getTagRegistReqDTOList() != null) { + plan.getTagRegistReqDTOList().forEach(tag -> { + logger.info("tagNum={}, tagNm={}", tag.getTagNum(), tag.getTagNm()); + }); + } else { + logger.info("태그 없음"); + } + }); + for (PlanRegistReqDTO plan : scheduleRegistReqDTO.getPlanRegistReqDTOList()) { + // int radius = // scheduleRegistReqDTO.getRadius(); + + // ---------- 1) 지역 번호로 x, y 좌표 검색 & Kakao 후보장소 수집(평탄화) ---------- if (plan.getRegionRegistReqDTOList() == null || plan.getRegionRegistReqDTOList().isEmpty()) { - // 지역이 없으면 스킵 logger.info("#$# skip: plan has no regions. plan={}", plan); continue; } int limitPlace = calLimitPlace(plan.getRegionRegistReqDTOList().size()); + + List> allKakaoPlaceResults = new ArrayList<>(); + String queryString = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); // 검색어 + for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { + // regionNum으로 좌표 가져오기 + RegionGeoCodeResDTO geo = regionGeoCodeService.getGeocode(region.getRegionNum()); + if (geo == null) { + logger.warn("#$# regionNum={} not found in DB, skip.", region.getRegionNum()); + continue; + } + + RegionRegistReqDTO updatedRegion = region.toBuilder() + .regionLevel1(geo.getRegionLevel1()) + .regionLevel2(geo.getRegionLevel2()) + .regionLevel3(geo.getRegionLevel3()) + .regionLevel4(geo.getRegionLevel4()) + .build(); + + // 리스트 교체 + int idx = plan.getRegionRegistReqDTOList().indexOf(region); + plan.getRegionRegistReqDTOList().set(idx, updatedRegion); + + // 지역명 결정 (레벨2/3 우선순위 적용) + String regionName = null; + if (updatedRegion.getRegionLevel3() != null && !updatedRegion.getRegionLevel3().isEmpty()) { + regionName = updatedRegion.getRegionLevel3(); + } else if (updatedRegion.getRegionLevel2() != null && !updatedRegion.getRegionLevel2().isEmpty()) { + regionName = updatedRegion.getRegionLevel2(); + } + List oneRegionPlaces = - kakaoPlaceClient.searchKakaoPlace(queryString, region.getX(), region.getY(), radius, limitPlace); + kakaoPlaceClient.searchKakaoPlace(queryString, geo.getX(), geo.getY(), radius, limitPlace); allKakaoPlaceResults.add(oneRegionPlaces); + + logger.info("#$# resolved regionName={} for regionNum={}", regionName, updatedRegion.getRegionNum()); + } + logger.info("allKakaoPlaceResults: {}", allKakaoPlaceResults); + + + // Kakao 평탄화(이 순서를 기준으로 이후 Naver/AI도 동일하게 맞춤) List flatKakao = allKakaoPlaceResults.stream() .filter(Objects::nonNull) @@ -115,7 +165,6 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe List placeList = new ArrayList<>(); for (int i = 0; i < flatKakao.size(); i++) { KakaoPlaceResDTO k = flatKakao.get(i); - // 대응되는 블로그가 없다면 간단한 설명을 생성(fallback) String title = k.getPlaceName(); String desc = Optional.ofNullable(k.getCategoryGroupName()).orElse("카테고리 정보 없음") @@ -135,8 +184,16 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe } // ---------- 3) AI 호출 ---------- - List tagNums = Optional.ofNullable(plan.getTagList()).orElseGet(List::of); + List tagNums = Optional.ofNullable(plan.getTagRegistReqDTOList()) + .orElseGet(List::of) + .stream() + .map(TagRegistReqDTO::getTagNum) + .collect(Collectors.toList()); + + logger.info("#$# 1 Before AI request"); + List tagNames = tagQueryService.findTagNmByTagNum(tagNums); + logger.info("#$# 2 Before AI request"); AIReqWrapper aiReqWrapper = AIReqWrapper.builder() .tags(tagNames) @@ -156,6 +213,20 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe KakaoPlaceResDTO chosen = flatKakao.get(idx); // ---------- 5) DB insert (예시) ---------- + // (1) Schedule + + // (2) Schedule_tag + + // (3) Plan + + // (4) Plan_tag + + // (5) Plan_region + + // (6) Place + + + // Plan 엔티티는 프로젝트 엔티티에 맞게 매핑 필요 // Plan planEntity = Plan.builder() // .planNm(chosen.getPlaceName()) @@ -183,7 +254,17 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe .planDescription(aiRes != null ? aiRes.getAiDescription() : null) .planAddress(Optional.ofNullable(chosen.getRoadAddressName()).orElse(chosen.getAddressName())) .regionName(null) // TODO. 지역 세팅 - firstRegionName(plan.getRegionRegistReqDTOList()) - .tagList(plan.getTagList()) + .tagRegistResDTOList( + Optional.ofNullable(plan.getTagRegistReqDTOList()) + .orElseGet(java.util.Collections::emptyList) + .stream() + .map(req -> TagRegistResDTO.builder() + .tagNum(req.getTagNum()) + .tagNm(req.getTagNm()) + .build() + ) + .collect(java.util.stream.Collectors.toList()) + ) .build(); planResList.add(planRes); diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index dc2bc79..1a1c267 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -7,6 +7,7 @@ import com.barogagi.plan.query.service.PlanQueryService; import com.barogagi.plan.query.vo.PlanDetailVO; import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.region.dto.RegionSearchResDTO; import com.barogagi.response.ApiResponse; import com.barogagi.schedule.command.service.ScheduleCommandService; import com.barogagi.schedule.dto.ScheduleDetailResDTO; @@ -124,7 +125,6 @@ public ApiResponse registSchedule( " \"scheduleNm\": \"서울 데이트 코스\",\n" + " \"startDate\": \"2025-07-01\",\n" + " \"endDate\": \"2025-07-01\",\n" + - " \"radius\": 3000,\n" + " \"comment\": \"분위기 좋은 카페 추천해주세요\",\n" + " \"planRegistReqDTOList\": [\n" + " {\n" + @@ -134,13 +134,13 @@ public ApiResponse registSchedule( " \"categoryNum\": 2,\n" + " \"regionRegistReqDTOList\": [\n" + " {\n" + - " \"regionNm1\": \"서울특별시\",\n" + - " \"regionNm2\": \"강남구\",\n" + - " \"x\": \"127.04892851392\",\n" + - " \"y\": \"37.5091105328378\"\n" + + " \"regionNum\": 1\n" + " }\n" + " ],\n" + - " \"tagList\": [1, 2]\n" + + " \"tagRegistReqDTOList\": [\n" + + " { \"tagNm\": \"디저트맛집\", \"tagNum\": 14 },\n" + + " { \"tagNm\": \"인스타핫플\", \"tagNum\": 15 }\n" + + " ]\n" + " },\n" + " {\n" + " \"startTime\": \"14:00\",\n" + @@ -149,13 +149,12 @@ public ApiResponse registSchedule( " \"categoryNum\": 1,\n" + " \"regionRegistReqDTOList\": [\n" + " {\n" + - " \"regionNm1\": \"서울특별시\",\n" + - " \"regionNm2\": \"강남구\",\n" + - " \"x\": \"127.046634887695\",\n" + - " \"y\": \"37.500690460205\"\n" + + " \"regionNum\": 1\n" + " }\n" + " ],\n" + - " \"tagList\": [2]\n" + + " \"tagRegistReqDTOList\": [\n" + + " { \"tagNm\": \"조용한\", \"tagNum\": 17 }\n" + + " ]\n" + " },\n" + " {\n" + " \"startTime\": \"15:30\",\n" + @@ -164,66 +163,37 @@ public ApiResponse registSchedule( " \"categoryNum\": 4,\n" + " \"regionRegistReqDTOList\": [\n" + " {\n" + - " \"regionNm1\": \"서울특별시\",\n" + - " \"regionNm2\": \"종로구\",\n" + - " \"x\": \"126.996652\",\n" + - " \"y\": \"37.579617\"\n" + + " \"regionNum\": 1\n" + + " },\n" + + " {\n" + + " \"regionNum\": 2\n" + " }\n" + " ],\n" + - " \"tagList\": [2]\n" + + " \"tagRegistReqDTOList\": [\n" + + " { \"tagNm\": \"테마파크\", \"tagNum\": 4 }\n" + + " ]\n" + " }\n" + " ]\n" + "}" ) ) ) - @RequestBody ScheduleRegistReqDTO scheduleRegistReqDTO) { - - logger.info("CALL /schedule"); + @RequestBody ScheduleRegistReqDTO scheduleRegistReqDTO + ) { - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; + logger.info("CALL /region/searchList"); + logger.info("[input] scheduleRegistReqDTO={}", scheduleRegistReqDTO); - ScheduleRegistResDTO scheduleRegistResDTO; + ScheduleRegistResDTO result; try { - //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ - if (true) { - boolean isVaildReq = true; // TODO. DTO 검증 로직 추가 필요 - if (!isVaildReq) { - resultCode = "101"; - message = "입력값이 올바르지 않습니다."; - } else { - scheduleRegistResDTO = scheduleCommandService.registSchedule(scheduleRegistReqDTO); - apiResponse.setData(scheduleRegistResDTO); - - /*if (result == null) { - resultCode = "300"; - message = "조회할 일정이 존재하지 않습니다."; // TODO. 에러 메시지 정의하기 - - } else { - resultCode = "200"; - message = "일정 상세 조회 성공"; - apiResponse.setData(result); - logger.info("#$# result={}", result.toString()); - }*/ - } + result = scheduleCommandService.registSchedule(scheduleRegistReqDTO); - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - logger.info("#$# 11 resultCode={}", resultCode); } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); + return ApiResponse.error("404", "일정 생성 실패"); } - return apiResponse; + + + return ApiResponse.success(result, "일정 생성 성공"); } } diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java index 2ec2251..6eaa214 100644 --- a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java @@ -16,12 +16,8 @@ public class ScheduleRegistReqDTO { private String scheduleNm; // 일정명 private String startDate; // 시작 날짜 private String endDate; // 종료 날짜 - private int radius; // 반경 private String comment; // 추가 고려사항 - // 지역 리스트 - // private List regionRegistReqDTOList; - // 계획 리스트 private List planRegistReqDTOList; } diff --git a/src/main/java/com/barogagi/tag/controller/TagController.java b/src/main/java/com/barogagi/tag/controller/TagController.java new file mode 100644 index 0000000..bac8f4e --- /dev/null +++ b/src/main/java/com/barogagi/tag/controller/TagController.java @@ -0,0 +1,54 @@ +package com.barogagi.tag.controller; + +import com.barogagi.response.ApiResponse; +import com.barogagi.schedule.dto.ScheduleRegistReqDTO; +import com.barogagi.tag.dto.TagSearchReqDTO; +import com.barogagi.tag.dto.TagSearchResDTO; +import com.barogagi.tag.query.service.TagQueryService; +import com.barogagi.util.InputValidate; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "태그", description = "태그 관련 API") +@RestController +@RequestMapping("/tag") +public class TagController { + private static final Logger logger = LoggerFactory.getLogger(TagController.class); + + private final InputValidate inputValidate; + + private final String API_SECRET_KEY; + + private final TagQueryService tagQueryService; + + public TagController(Environment environment, InputValidate inputValidate, + TagQueryService tagQueryService) { + this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + this.inputValidate = inputValidate; + this.tagQueryService = tagQueryService; + } + + @Operation( + summary = "태그 목록 검색", + description = "태그 목록을 검색하는 기능입니다.
" + + "- 여행 스타일 태그(S): categoryNum을 null로 전달하세요.
" + + "- 상세 일정 태그(P): 해당 일정의 카테고리 번호(categoryNum)를 전달하세요.
" + + "검색 결과는 최대 10개의 태그를 반환합니다." + ) + @PostMapping("/searchList") + public ApiResponse searchList(@RequestBody TagSearchReqDTO tagSearchReqDTO) { + + logger.info("CALL /tag/searchList"); + + List result = tagQueryService.searchList(tagSearchReqDTO); + return ApiResponse.success(result, "태그 목록 검색 성공"); + + } + +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/dto/TagRegistResDTO.java b/src/main/java/com/barogagi/tag/dto/TagRegistResDTO.java new file mode 100644 index 0000000..2671ef6 --- /dev/null +++ b/src/main/java/com/barogagi/tag/dto/TagRegistResDTO.java @@ -0,0 +1,18 @@ +package com.barogagi.tag.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@Builder(toBuilder = true) +@Schema(description = "태그 정보 리스트 DTO") +public class TagRegistResDTO { + @Schema(description = "태그 번호", example = "1") + public int tagNum; + + @Schema(description = "태그 이름", example = "디저트") + public String tagNm; +} diff --git a/src/main/java/com/barogagi/tag/dto/TagSearchReqDTO.java b/src/main/java/com/barogagi/tag/dto/TagSearchReqDTO.java new file mode 100644 index 0000000..4dc33bb --- /dev/null +++ b/src/main/java/com/barogagi/tag/dto/TagSearchReqDTO.java @@ -0,0 +1,14 @@ +package com.barogagi.tag.dto; + +import com.barogagi.tag.enums.TagType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.ToString; + +public class TagSearchReqDTO { + @Schema(description = "태그 타입 (S는 스타일 태그, P는 세부 일정 태그)", example = "P") + public TagType tagType; + + @Schema(description = "카테고리 번호 (스타일 태그인 경우 null)", example = "1") + public Integer categoryNum; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/dto/TagSearchResDTO.java b/src/main/java/com/barogagi/tag/dto/TagSearchResDTO.java new file mode 100644 index 0000000..7c319f9 --- /dev/null +++ b/src/main/java/com/barogagi/tag/dto/TagSearchResDTO.java @@ -0,0 +1,16 @@ +package com.barogagi.tag.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@Schema(description = "태그 검색 결과 DTO") +public class TagSearchResDTO { + @Schema(description = "태그 번호", example = "1") + public int tagNum; + + @Schema(description = "태그 이름", example = "디저트") + public String tagNm; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/enums/TagType.java b/src/main/java/com/barogagi/tag/enums/TagType.java new file mode 100644 index 0000000..d4659a6 --- /dev/null +++ b/src/main/java/com/barogagi/tag/enums/TagType.java @@ -0,0 +1,31 @@ +package com.barogagi.tag.enums; + +public enum TagType { + S("일정별 태그"), + P("계획별 태그"); + + private final String description; + + TagType(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + // DB 저장용 코드 반환 + public String getCode() { + return this.name(); + } + + // 코드로부터 Enum 찾기 + public static TagType fromCode(String code) { + for (TagType type : TagType.values()) { + if (type.name().equalsIgnoreCase(code)) { + return type; + } + } + throw new IllegalArgumentException("Unknown TagType code: " + code); + } +} diff --git a/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java b/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java index 1aa7eb2..00a0c62 100644 --- a/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java +++ b/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java @@ -1,5 +1,7 @@ package com.barogagi.tag.query.mapper; +import com.barogagi.tag.dto.TagSearchReqDTO; +import com.barogagi.tag.dto.TagSearchResDTO; import com.barogagi.tag.query.vo.TagDetailVO; import org.apache.ibatis.annotations.Mapper; @@ -13,4 +15,7 @@ public interface TagMapper { // 일정 생성 - 태그 번호로 태그명 조회 TagDetailVO selectTagByTagNum (int tagNum); + + // 태그 리스트 조회 + List selectTagByTagTypeAndCategoryNum(TagSearchReqDTO tagSearchReqDTO); } diff --git a/src/main/java/com/barogagi/tag/query/service/TagQueryService.java b/src/main/java/com/barogagi/tag/query/service/TagQueryService.java index 3612ef6..bf1e3ee 100644 --- a/src/main/java/com/barogagi/tag/query/service/TagQueryService.java +++ b/src/main/java/com/barogagi/tag/query/service/TagQueryService.java @@ -1,7 +1,13 @@ package com.barogagi.tag.query.service; +import com.barogagi.schedule.command.service.ScheduleCommandService; +import com.barogagi.schedule.dto.ScheduleRegistReqDTO; +import com.barogagi.tag.dto.TagSearchReqDTO; +import com.barogagi.tag.dto.TagSearchResDTO; import com.barogagi.tag.query.mapper.TagMapper; import com.barogagi.tag.query.vo.TagDetailVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.stream.Collectors; @@ -10,6 +16,7 @@ @Service public class TagQueryService { + private static final Logger logger = LoggerFactory.getLogger(TagQueryService.class); private final TagMapper tagMapper; @@ -30,4 +37,8 @@ public List findTagNmByTagNum(List tagNums) { .map(TagDetailVO::getTagNm) .collect(Collectors.toList()); } + + public List searchList(TagSearchReqDTO tagSearchReqDTO) { + return tagMapper.selectTagByTagTypeAndCategoryNum(tagSearchReqDTO); + } } \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/query/vo/TagDetailVO.java b/src/main/java/com/barogagi/tag/query/vo/TagDetailVO.java index 4748a9b..382fd4c 100644 --- a/src/main/java/com/barogagi/tag/query/vo/TagDetailVO.java +++ b/src/main/java/com/barogagi/tag/query/vo/TagDetailVO.java @@ -1,5 +1,6 @@ package com.barogagi.tag.query.vo; +import com.barogagi.tag.enums.TagType; import lombok.Getter; import lombok.ToString; @@ -8,4 +9,6 @@ public class TagDetailVO { private int tagNum; // 태그 번호 private String tagNm; // 태그명 + private TagType tagType; // 태그 타입 + private int categoryNum; // 카테고리 번호 (일정(스타일) 태그는 null, 계획(plan) 태그는 카테고리 번호) } diff --git a/src/main/resources/mapper/TagMapper.xml b/src/main/resources/mapper/TagMapper.xml index 46d4166..f68692c 100644 --- a/src/main/resources/mapper/TagMapper.xml +++ b/src/main/resources/mapper/TagMapper.xml @@ -18,12 +18,40 @@ + + + + + + + From a51e1530db2640d42248210dd91de2374ce69bbf Mon Sep 17 00:00:00 2001 From: RohDamin Date: Thu, 9 Oct 2025 17:15:23 +0900 Subject: [PATCH 18/79] =?UTF-8?q?fix:=20=EC=9D=BC=EC=A0=95=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kakaoplace/dto/KakaoPlaceResDTO.java | 4 +- .../barogagi/plan/command/entity/Plan.java | 6 + .../command/repository/ItemRepository.java | 9 + .../command/repository/PlanTagRepository.java | 7 + .../barogagi/plan/dto/PlanRegistReqDTO.java | 4 +- .../barogagi/plan/dto/PlanRegistResDTO.java | 10 +- .../plan/query/mapper/ItemMapper.java | 10 + .../barogagi/region/command/entity/Place.java | 40 +++ .../region/command/entity/PlanRegion.java | 17 +- .../region/command/entity/PlanRegionId.java | 19 ++ .../region/command/entity/PlanRegionNum.java | 22 -- .../command/repository/PlaceRepository.java | 7 + .../repository/PlanRegionRepository.java | 7 + .../schedule/command/entity/Schedule.java | 7 + .../service/ScheduleCommandService.java | 274 ++++++++++++++---- .../controller/ScheduleController.java | 18 +- .../schedule/dto/ScheduleRegistReqDTO.java | 5 + .../schedule/dto/ScheduleRegistResDTO.java | 1 + .../barogagi/tag/command/entity/PlanTag.java | 28 ++ .../tag/command/entity/PlanTagId.java | 19 ++ .../tag/command/entity/ScheduleTag.java | 29 ++ .../tag/command/entity/ScheduleTagId.java | 19 ++ .../com/barogagi/tag/command/entity/Tag.java | 33 +++ .../repository/ScheduleTagRepository.java | 11 + .../tag/command/repository/TagRepository.java | 12 + src/main/resources/mapper/ItemMapper.xml | 9 +- 26 files changed, 526 insertions(+), 101 deletions(-) create mode 100644 src/main/java/com/barogagi/plan/command/repository/ItemRepository.java create mode 100644 src/main/java/com/barogagi/plan/command/repository/PlanTagRepository.java create mode 100644 src/main/java/com/barogagi/plan/query/mapper/ItemMapper.java create mode 100644 src/main/java/com/barogagi/region/command/entity/Place.java create mode 100644 src/main/java/com/barogagi/region/command/entity/PlanRegionId.java delete mode 100644 src/main/java/com/barogagi/region/command/entity/PlanRegionNum.java create mode 100644 src/main/java/com/barogagi/region/command/repository/PlaceRepository.java create mode 100644 src/main/java/com/barogagi/region/command/repository/PlanRegionRepository.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/PlanTag.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/PlanTagId.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/ScheduleTagId.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/Tag.java create mode 100644 src/main/java/com/barogagi/tag/command/repository/ScheduleTagRepository.java create mode 100644 src/main/java/com/barogagi/tag/command/repository/TagRepository.java diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java index 45e6dbc..815c7d0 100644 --- a/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java @@ -4,7 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -@Getter +@Getter @Setter @ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 @AllArgsConstructor @@ -41,4 +41,6 @@ public class KakaoPlaceResDTO { @JsonProperty("phone") private String phone; + + private Integer regionNum; } diff --git a/src/main/java/com/barogagi/plan/command/entity/Plan.java b/src/main/java/com/barogagi/plan/command/entity/Plan.java index c71ad9a..5312a12 100644 --- a/src/main/java/com/barogagi/plan/command/entity/Plan.java +++ b/src/main/java/com/barogagi/plan/command/entity/Plan.java @@ -1,6 +1,7 @@ package com.barogagi.plan.command.entity; import com.barogagi.plan.command.ex_entity.PlanUserMembershipInfo; +import com.barogagi.region.command.entity.Place; import com.barogagi.schedule.command.entity.Schedule; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.*; @@ -48,4 +49,9 @@ public class Plan { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "ITEM_NUM", nullable = false) private Item item; // 아이템 번호 + + // PLACE와 1:1 mapping + @OneToOne(mappedBy = "plan", cascade = CascadeType.ALL, orphanRemoval = true) + private Place place; + } diff --git a/src/main/java/com/barogagi/plan/command/repository/ItemRepository.java b/src/main/java/com/barogagi/plan/command/repository/ItemRepository.java new file mode 100644 index 0000000..1e6737f --- /dev/null +++ b/src/main/java/com/barogagi/plan/command/repository/ItemRepository.java @@ -0,0 +1,9 @@ +package com.barogagi.plan.command.repository; + +import com.barogagi.plan.command.entity.Item; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ItemRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/plan/command/repository/PlanTagRepository.java b/src/main/java/com/barogagi/plan/command/repository/PlanTagRepository.java new file mode 100644 index 0000000..a6a6027 --- /dev/null +++ b/src/main/java/com/barogagi/plan/command/repository/PlanTagRepository.java @@ -0,0 +1,7 @@ +package com.barogagi.plan.command.repository; + +import com.barogagi.tag.command.entity.PlanTag; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlanTagRepository extends JpaRepository { +} diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java index d378a5c..2a50b61 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java @@ -33,7 +33,7 @@ public class PlanRegistReqDTO { @Schema(description = "지역 정보 DTO") public List regionRegistReqDTOList; - @Schema(description = "태그 목록") - public List tagRegistReqDTOList; + @Schema(description = "계획 태그 목록") + public List planTagRegistReqDTOList; } diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java index f05ba2e..15d22eb 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java @@ -1,5 +1,6 @@ package com.barogagi.plan.dto; +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; import com.barogagi.region.dto.RegionRegistReqDTO; import com.barogagi.tag.dto.TagRegistReqDTO; import com.barogagi.tag.dto.TagRegistResDTO; @@ -35,7 +36,7 @@ public class PlanRegistResDTO { public String categoryNm; @Schema(description = "장소 번호") - public String planNum; + public Integer planNum; @Schema(description = "장소명") public String planNm; @@ -50,9 +51,14 @@ public class PlanRegistResDTO { public String planAddress; @Schema(description = "지역 정보 DTO") - public String regionName; + public String regionNm; + + @Schema(description = "지역 번호") + public Integer regionNum; @Schema(description = "태그 목록") public List tagRegistResDTOList; + + // private KakaoPlaceResDTO aiChosen; } diff --git a/src/main/java/com/barogagi/plan/query/mapper/ItemMapper.java b/src/main/java/com/barogagi/plan/query/mapper/ItemMapper.java new file mode 100644 index 0000000..a0f4fa2 --- /dev/null +++ b/src/main/java/com/barogagi/plan/query/mapper/ItemMapper.java @@ -0,0 +1,10 @@ +package com.barogagi.plan.query.mapper; + +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ItemMapper { + + // 세부 카테고리(아이템)명 조회 + String selectItemNmBy(int itemNum); +} diff --git a/src/main/java/com/barogagi/region/command/entity/Place.java b/src/main/java/com/barogagi/region/command/entity/Place.java new file mode 100644 index 0000000..1f5bc5a --- /dev/null +++ b/src/main/java/com/barogagi/region/command/entity/Place.java @@ -0,0 +1,40 @@ +package com.barogagi.region.command.entity; + +import com.barogagi.plan.command.entity.Plan; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Table(name = "PLACE") +public class Place { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "PLACE_NUM") + private Integer placeNum; + + @Column(name = "REGION_NM", nullable = false) + private String regionNm; + + @Column(name = "ADDRESS") + private String address; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "REGION_NUM") + private Region region; + + @Column(name = "PLAN_LINK") + private String planLink; + + @Column(name = "PLACE_DESCRIPTION") + private String placeDescription; + + // PLAN과 1:1 mapping + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "PLAN_NUM", unique = true) + private Plan plan; +} diff --git a/src/main/java/com/barogagi/region/command/entity/PlanRegion.java b/src/main/java/com/barogagi/region/command/entity/PlanRegion.java index 4250b1f..d4e6433 100644 --- a/src/main/java/com/barogagi/region/command/entity/PlanRegion.java +++ b/src/main/java/com/barogagi/region/command/entity/PlanRegion.java @@ -1,8 +1,7 @@ package com.barogagi.region.command.entity; -import jakarta.persistence.EmbeddedId; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; +import com.barogagi.plan.command.entity.Plan; +import jakarta.persistence.*; import lombok.*; @Entity @@ -14,5 +13,15 @@ public class PlanRegion { @EmbeddedId - private PlanRegionNum planRegionNum; // (복합키) 계획 번호, 지역 번호 + private PlanRegionId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("planNum") + @JoinColumn(name = "PLAN_NUM") + private Plan plan; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("regionNum") + @JoinColumn(name = "REGION_NUM") + private Region region; } diff --git a/src/main/java/com/barogagi/region/command/entity/PlanRegionId.java b/src/main/java/com/barogagi/region/command/entity/PlanRegionId.java new file mode 100644 index 0000000..ec048ea --- /dev/null +++ b/src/main/java/com/barogagi/region/command/entity/PlanRegionId.java @@ -0,0 +1,19 @@ +package com.barogagi.region.command.entity; + +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@Embeddable +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PlanRegionId implements Serializable { + private Integer regionNum; + private Integer planNum; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/region/command/entity/PlanRegionNum.java b/src/main/java/com/barogagi/region/command/entity/PlanRegionNum.java deleted file mode 100644 index 30d12ef..0000000 --- a/src/main/java/com/barogagi/region/command/entity/PlanRegionNum.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.barogagi.region.command.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Embeddable -@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 -@AllArgsConstructor -public class PlanRegionNum { - // 계획별 지역 복합키 - - @Column(name="REGION_NUM") - private Integer regionNum; // 지역 번호 - - @Column(name="PLAN_NUM") - private Integer planNum; // 계획 변호 -} diff --git a/src/main/java/com/barogagi/region/command/repository/PlaceRepository.java b/src/main/java/com/barogagi/region/command/repository/PlaceRepository.java new file mode 100644 index 0000000..217127a --- /dev/null +++ b/src/main/java/com/barogagi/region/command/repository/PlaceRepository.java @@ -0,0 +1,7 @@ +package com.barogagi.region.command.repository; + +import com.barogagi.region.command.entity.Place; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceRepository extends JpaRepository { +} diff --git a/src/main/java/com/barogagi/region/command/repository/PlanRegionRepository.java b/src/main/java/com/barogagi/region/command/repository/PlanRegionRepository.java new file mode 100644 index 0000000..eddaedd --- /dev/null +++ b/src/main/java/com/barogagi/region/command/repository/PlanRegionRepository.java @@ -0,0 +1,7 @@ +package com.barogagi.region.command.repository; + +import com.barogagi.region.command.entity.PlanRegion; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlanRegionRepository extends JpaRepository { +} diff --git a/src/main/java/com/barogagi/schedule/command/entity/Schedule.java b/src/main/java/com/barogagi/schedule/command/entity/Schedule.java index 7d5c65c..334ed6f 100644 --- a/src/main/java/com/barogagi/schedule/command/entity/Schedule.java +++ b/src/main/java/com/barogagi/schedule/command/entity/Schedule.java @@ -1,8 +1,12 @@ package com.barogagi.schedule.command.entity; +import com.barogagi.plan.command.entity.Plan; import jakarta.persistence.*; import lombok.*; +import java.util.ArrayList; +import java.util.List; + @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 @@ -30,4 +34,7 @@ public class Schedule { @Column(name = "RADIUS", nullable = false) private int radius; // 추천 반경 (미터 단위) + + @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true) + private List plans = new ArrayList<>(); } \ No newline at end of file diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 81c9dde..8390fd0 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -8,15 +8,33 @@ import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; import com.barogagi.naverblog.client.NaverBlogClient; import com.barogagi.naverblog.dto.NaverBlogResDTO; +import com.barogagi.plan.command.entity.Item; +import com.barogagi.plan.command.entity.Plan; +import com.barogagi.plan.command.repository.ItemRepository; +import com.barogagi.plan.command.repository.PlanRepository; +import com.barogagi.plan.command.repository.PlanTagRepository; import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.dto.PlanRegistResDTO; import com.barogagi.plan.query.mapper.CategoryMapper; +import com.barogagi.plan.query.mapper.ItemMapper; +import com.barogagi.region.command.entity.Place; +import com.barogagi.region.command.entity.PlanRegion; +import com.barogagi.region.command.entity.PlanRegionId; +import com.barogagi.region.command.entity.Region; +import com.barogagi.region.command.repository.PlaceRepository; +import com.barogagi.region.command.repository.PlanRegionRepository; +import com.barogagi.region.command.repository.RegionRepository; import com.barogagi.region.dto.RegionGeoCodeResDTO; import com.barogagi.region.dto.RegionRegistReqDTO; import com.barogagi.region.query.service.RegionGeoCodeService; import com.barogagi.region.query.service.RegionQueryService; +import com.barogagi.schedule.command.entity.Schedule; +import com.barogagi.schedule.command.repository.ScheduleRepository; import com.barogagi.schedule.dto.ScheduleRegistReqDTO; import com.barogagi.schedule.dto.ScheduleRegistResDTO; +import com.barogagi.tag.command.entity.*; +import com.barogagi.tag.command.repository.ScheduleTagRepository; +import com.barogagi.tag.command.repository.TagRepository; import com.barogagi.tag.dto.TagRegistReqDTO; import com.barogagi.tag.dto.TagRegistResDTO; import com.barogagi.tag.query.service.TagQueryService; @@ -25,8 +43,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; import java.util.*; import java.util.stream.Collectors; @@ -37,6 +57,7 @@ public class ScheduleCommandService { private static final Logger logger = LoggerFactory.getLogger(ScheduleCommandService.class); private final CategoryMapper categoryMapper; + private final ItemMapper itemMapper; private final KakaoPlaceClient kakaoPlaceClient; private final NaverBlogClient naverBlogClient; private final AIClient aiClient; @@ -44,6 +65,16 @@ public class ScheduleCommandService { private final TagQueryService tagQueryService; private final RegionGeoCodeService regionGeoCodeService; + private final ScheduleRepository scheduleRepository; + private final ScheduleTagRepository scheduleTagRepository; + private final TagRepository tagRepository; + private final ItemRepository itemRepository; + private final PlanRepository planRepository; + private final PlanTagRepository planTagRepository; + private final RegionRepository regionRepository; + private final PlanRegionRepository planRegionRepository; + private final PlaceRepository placeRepository; + @Value("${kakao.radius}") private int radius; @@ -52,19 +83,33 @@ public class ScheduleCommandService { private int naverBlogDisplay; @Autowired - public ScheduleCommandService(CategoryMapper categoryMapper, + public ScheduleCommandService(CategoryMapper categoryMapper, ItemMapper itemMapper, KakaoPlaceClient kakaoPlaceClient, NaverBlogClient naverBlogClient, - AIClient aiClient, TagQueryService tagQueryService, RegionGeoCodeService regionGeoCodeService) { + AIClient aiClient, TagQueryService tagQueryService, RegionGeoCodeService regionGeoCodeService, + ScheduleRepository scheduleRepository, ScheduleTagRepository scheduleTagRepository, + TagRepository tagRepository, ItemRepository itemRepository, + PlanRepository planRepository, PlanTagRepository planTagRepository, + RegionRepository regionRepository, PlanRegionRepository planRegionRepository, + PlaceRepository placeRepository) { + this.itemMapper = itemMapper; this.categoryMapper = categoryMapper; this.kakaoPlaceClient = kakaoPlaceClient; this.naverBlogClient = naverBlogClient; this.aiClient = aiClient; this.tagQueryService = tagQueryService; this.regionGeoCodeService = regionGeoCodeService; + this.scheduleRepository = scheduleRepository; + this.scheduleTagRepository = scheduleTagRepository; + this.tagRepository = tagRepository; + this.itemRepository = itemRepository; + this.planRepository = planRepository; + this.planTagRepository = planTagRepository; + this.regionRepository = regionRepository; + this.planRegionRepository = planRegionRepository; + this.placeRepository = placeRepository; } - @Transactional - public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { + public ScheduleRegistResDTO createSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { List planResList = new ArrayList<>(); @@ -75,8 +120,8 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe scheduleRegistReqDTO.getPlanRegistReqDTOList().forEach(plan -> { - if (plan.getTagRegistReqDTOList() != null) { - plan.getTagRegistReqDTOList().forEach(tag -> { + if (plan.getPlanTagRegistReqDTOList() != null) { + plan.getPlanTagRegistReqDTOList().forEach(tag -> { logger.info("tagNum={}, tagNm={}", tag.getTagNum(), tag.getTagNm()); }); } else { @@ -96,7 +141,11 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe int limitPlace = calLimitPlace(plan.getRegionRegistReqDTOList().size()); List> allKakaoPlaceResults = new ArrayList<>(); - String queryString = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); // 검색어 + + String categoryNm = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); + String queryString = categoryNm; // 검색어 + + String itemNm = itemMapper.selectItemNmBy(plan.getItemNum()); // todo. itemNm을 검색어로 쓸지 고려하기 for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { // regionNum으로 좌표 가져오기 @@ -129,6 +178,11 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe kakaoPlaceClient.searchKakaoPlace(queryString, geo.getX(), geo.getY(), radius, limitPlace); allKakaoPlaceResults.add(oneRegionPlaces); + // 각 장소에 regionNum 세팅 + if (oneRegionPlaces != null) { + oneRegionPlaces.forEach(k -> k.setRegionNum(region.getRegionNum())); + } + logger.info("#$# resolved regionName={} for regionNum={}", regionName, updatedRegion.getRegionNum()); } @@ -184,16 +238,14 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe } // ---------- 3) AI 호출 ---------- - List tagNums = Optional.ofNullable(plan.getTagRegistReqDTOList()) + // todo. 일정 전체에 대한 태그(schedulePlanTagRegistReqDTOList)도 참고하도록 수정해야 함 + List tagNums = Optional.ofNullable(plan.getPlanTagRegistReqDTOList()) .orElseGet(List::of) .stream() .map(TagRegistReqDTO::getTagNum) .collect(Collectors.toList()); - logger.info("#$# 1 Before AI request"); - List tagNames = tagQueryService.findTagNmByTagNum(tagNums); - logger.info("#$# 2 Before AI request"); AIReqWrapper aiReqWrapper = AIReqWrapper.builder() .tags(tagNames) @@ -202,75 +254,61 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe .build(); AIResDTO aiRes = aiClient.recommandPlace(aiReqWrapper); - logger.info("#$# AI Recommendation result obj={}", aiRes); // ---------- 4) AI가 고른 index → Kakao place 선택 ---------- Integer idx = (aiRes != null) ? aiRes.getRecommandPlaceIndex() : null; if (idx == null || idx < 0 || idx >= flatKakao.size()) { - logger.warn("#$# invalid recommandPlaceNum={}, fallback to 0", idx); idx = 0; // fallback } - KakaoPlaceResDTO chosen = flatKakao.get(idx); - - // ---------- 5) DB insert (예시) ---------- - // (1) Schedule - - // (2) Schedule_tag - - // (3) Plan - - // (4) Plan_tag - - // (5) Plan_region - - // (6) Place - - - - // Plan 엔티티는 프로젝트 엔티티에 맞게 매핑 필요 - // Plan planEntity = Plan.builder() - // .planNm(chosen.getPlaceName()) - // .planLink(chosen.getPlaceUrl()) - // .planDescription(aiRes != null ? aiRes.getAiDescription() : null) - // .planAddress(Optional.ofNullable(chosen.getRoadAddressName()).orElse(chosen.getAddressName())) - // .itemNum(plan.getItemNum()) - // .categoryNum(plan.getCategoryNum()) - // .startTime(plan.getStartTime()) - // .endTime(plan.getEndTime()) - // .build(); - // planRepository.save(planEntity); + KakaoPlaceResDTO aiChosen = flatKakao.get(idx); + + // ---------- 5) 응답 DTO 생성 ---------- + String regionNm = null; + if (aiChosen.getRegionNum() != null) { + regionNm = regionRepository.findById(aiChosen.getRegionNum()) + .map(region -> { + // 지역명은 보통 3레벨 > 2레벨 순으로 선택 + if (region.getRegionLevel3() != null && !region.getRegionLevel3().isEmpty()) + return region.getRegionLevel3(); + else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty()) + return region.getRegionLevel2(); + else + return region.getRegionLevel1(); + }) + .orElse(null); + } - // ---------- 6) PlanRegistResDTO 구성 ---------- PlanRegistResDTO planRes = PlanRegistResDTO.builder() - .planNum(null) // TODO. DB 저장 후 세팅 - .startTime(plan.getStartTime()) - .endTime(plan.getEndTime()) - .itemNum(plan.getItemNum()) - .itemNm(null) // TODO. itemNm(plan.getItemNm()) - .categoryNum(plan.getCategoryNum()) - .categoryNm(null)// TODO. categoryNm(plan.getCategoryNm()) - .planNm(chosen.getPlaceName()) - .planLink(chosen.getPlaceUrl()) + .planNm(aiChosen.getPlaceName()) + .planLink(aiChosen.getPlaceUrl()) .planDescription(aiRes != null ? aiRes.getAiDescription() : null) - .planAddress(Optional.ofNullable(chosen.getRoadAddressName()).orElse(chosen.getAddressName())) - .regionName(null) // TODO. 지역 세팅 - firstRegionName(plan.getRegionRegistReqDTOList()) + .planAddress(Optional.ofNullable(aiChosen.getRoadAddressName()).orElse(aiChosen.getAddressName())) + .regionNm(regionNm) + .regionNum(aiChosen.getRegionNum()) + .categoryNm(categoryNm) + .categoryNum(plan.getCategoryNum()) + .itemNm(itemNm) + .itemNum(plan.getItemNum()) .tagRegistResDTOList( - Optional.ofNullable(plan.getTagRegistReqDTOList()) - .orElseGet(java.util.Collections::emptyList) + Optional.ofNullable(plan.getPlanTagRegistReqDTOList()) + .orElseGet(List::of) .stream() - .map(req -> TagRegistResDTO.builder() - .tagNum(req.getTagNum()) - .tagNm(req.getTagNm()) - .build() - ) - .collect(java.util.stream.Collectors.toList()) + .map(tagReq -> TagRegistResDTO.builder() + .tagNum(tagReq.getTagNum()) + .tagNm(tagReq.getTagNm()) + .build()) + .collect(Collectors.toList()) ) + // .aiChosen(aiChosen) .build(); planResList.add(planRes); } + // ---------- 6) DB insert ---------- +// Schedule savedSchedule = registScheduleInfo(scheduleRegistReqDTO, planResList); + // ---------- 7) ScheduleRegistResDTO 묶어서 리턴 ---------- return ScheduleRegistResDTO.builder() .scheduleNm(scheduleNm) @@ -280,6 +318,118 @@ public ScheduleRegistResDTO registSchedule(ScheduleRegistReqDTO scheduleRegistRe .build(); } +// @Transactional + @Transactional(propagation = Propagation.REQUIRES_NEW) + public Schedule registScheduleInfo(ScheduleRegistReqDTO scheduleRegistReqDTO, List planResList) { + logger.info("#$# START DB SAVE!"); + + // (1) Schedule + Schedule schedule = Schedule.builder() + .membershipNo(1) + .scheduleNm(scheduleRegistReqDTO.getScheduleNm()) + .startDate(scheduleRegistReqDTO.getStartDate()) + .endDate(scheduleRegistReqDTO.getEndDate()) + .radius(radius) + .build(); + + scheduleRepository.save(schedule); + logger.info("#$# schedule Save! scheduleNum={}", schedule.getScheduleNum()); + + // (2) Schedule_tag + if (scheduleRegistReqDTO.getScheduleTagRegistReqDTOList() != null) { + for (TagRegistReqDTO tagReq : scheduleRegistReqDTO.getScheduleTagRegistReqDTOList()) { + Tag tag = tagRepository.findById(tagReq.getTagNum()) + .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagReq.getTagNum())); + + scheduleTagRepository.save( + ScheduleTag.builder() + .id(new ScheduleTagId(tag.getTagNum(), schedule.getScheduleNum())) + .schedule(schedule) + .tag(tag) + .build() + ); + } + } + logger.info("#$# scheduleTag Save! scheduleNum={}", schedule.getScheduleNum()); + + // (3) Plan + Plan_tag + Plan_region + Place + for (int i = 0; i < scheduleRegistReqDTO.getPlanRegistReqDTOList().size(); i++) { + + PlanRegistReqDTO planReq = scheduleRegistReqDTO.getPlanRegistReqDTOList().get(i); + PlanRegistResDTO planRes = planResList.get(i); // AI 결과 매칭 + logger.info("#$# planRes={}", planRes); + + Item item = itemRepository.findById(planReq.getItemNum()) + .orElseThrow(() -> new IllegalArgumentException("Item not found: " + planReq.getItemNum())); + logger.info("#$# item={}", item); + + // ① Plan 저장 + Plan plan = Plan.builder() + .startTime(planReq.getStartTime()) + .endTime(planReq.getEndTime()) + .schedule(schedule) + .item(item) + .build(); + + planRepository.saveAndFlush(plan); + logger.info("✅ Plan saved: planNum={}", plan.getPlanNum()); + + + // ② Plan_tag + if (planReq.getPlanTagRegistReqDTOList() != null) { + for (TagRegistReqDTO tagReq : planReq.getPlanTagRegistReqDTOList()) { + Tag tag = tagRepository.findById(tagReq.getTagNum()) + .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagReq.getTagNum())); + + planTagRepository.save( + PlanTag.builder() + .id(new PlanTagId(tag.getTagNum(), plan.getPlanNum())) + .plan(plan) + .tag(tag) + .build() + ); + } + } + + // ③ Plan_region + if (planReq.getRegionRegistReqDTOList() != null) { + for (RegionRegistReqDTO regionReq : planReq.getRegionRegistReqDTOList()) { + Region region = regionRepository.findById(regionReq.getRegionNum()) + .orElseThrow(() -> new IllegalArgumentException("Region not found: " + regionReq.getRegionNum())); + + planRegionRepository.save( + PlanRegion.builder() + .id(new PlanRegionId(region.getRegionNum(), plan.getPlanNum())) + .plan(plan) + .region(region) + .build() + ); + } + } + + // ④ Place + if (planRes.getRegionNum() != null) { + Region region = regionRepository.findById(planRes.getRegionNum()) + .orElseThrow(() -> new IllegalArgumentException("Region not found: " + planRes.getRegionNum())); + + Place place = Place.builder() + .region(region) + .regionNm(region.getRegionLevel3() != null ? region.getRegionLevel3() : region.getRegionLevel2()) + .address(planRes.getPlanAddress()) + .planLink(planRes.getPlanLink()) + .placeDescription(planRes.getPlanDescription()) + .plan(plan) + .build(); + + placeRepository.save(place); + logger.info("✅ Place saved for planNum={}, regionNum={}", plan.getPlanNum(), planRes.getRegionNum()); + } + } + + return schedule; + } + + // 후보지역 수에 따라 각 지역의 후보장소 수를 리턴 // 후보장소 수만큼 네이버 블로그 API를 호출해야 하기 때문에 제한 필요 diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 1a1c267..8d5d78b 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -111,9 +111,9 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 } - @Operation(summary = "일정 등록 기능", description = "일정을 등록하는 기능입니다.") + @Operation(summary = "일정 생성 기능", description = "일정을 생성하는 기능입니다.") @PostMapping("") - public ApiResponse registSchedule( + public ApiResponse createSchedule( @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "일정 등록 요청", required = true, @@ -126,6 +126,10 @@ public ApiResponse registSchedule( " \"startDate\": \"2025-07-01\",\n" + " \"endDate\": \"2025-07-01\",\n" + " \"comment\": \"분위기 좋은 카페 추천해주세요\",\n" + + " \"scheduleTagRegistReqDTOList\": [\n" + + " { \"tagNm\": \"핫플\", \"tagNum\": 5 },\n" + + " { \"tagNm\": \"활동적인\", \"tagNum\": 8 }\n" + + " ],\n" + " \"planRegistReqDTOList\": [\n" + " {\n" + " \"startTime\": \"08:00\",\n" + @@ -137,7 +141,7 @@ public ApiResponse registSchedule( " \"regionNum\": 1\n" + " }\n" + " ],\n" + - " \"tagRegistReqDTOList\": [\n" + + " \"planTagRegistReqDTOList\": [\n" + " { \"tagNm\": \"디저트맛집\", \"tagNum\": 14 },\n" + " { \"tagNm\": \"인스타핫플\", \"tagNum\": 15 }\n" + " ]\n" + @@ -152,7 +156,7 @@ public ApiResponse registSchedule( " \"regionNum\": 1\n" + " }\n" + " ],\n" + - " \"tagRegistReqDTOList\": [\n" + + " \"planTagRegistReqDTOList\": [\n" + " { \"tagNm\": \"조용한\", \"tagNum\": 17 }\n" + " ]\n" + " },\n" + @@ -169,7 +173,7 @@ public ApiResponse registSchedule( " \"regionNum\": 2\n" + " }\n" + " ],\n" + - " \"tagRegistReqDTOList\": [\n" + + " \"planTagRegistReqDTOList\": [\n" + " { \"tagNm\": \"테마파크\", \"tagNum\": 4 }\n" + " ]\n" + " }\n" + @@ -181,13 +185,13 @@ public ApiResponse registSchedule( @RequestBody ScheduleRegistReqDTO scheduleRegistReqDTO ) { - logger.info("CALL /region/searchList"); + logger.info("CALL /schedule"); logger.info("[input] scheduleRegistReqDTO={}", scheduleRegistReqDTO); ScheduleRegistResDTO result; try { //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ - result = scheduleCommandService.registSchedule(scheduleRegistReqDTO); + result = scheduleCommandService.createSchedule(scheduleRegistReqDTO); } catch (Exception e) { return ApiResponse.error("404", "일정 생성 실패"); diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java index 6eaa214..5a8ba4c 100644 --- a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java @@ -3,6 +3,8 @@ import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.query.vo.PlanDetailVO; import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.tag.dto.TagRegistReqDTO; +import com.barogagi.tag.dto.TagRegistResDTO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.ToString; @@ -18,6 +20,9 @@ public class ScheduleRegistReqDTO { private String endDate; // 종료 날짜 private String comment; // 추가 고려사항 + // 일정 태그 목록 (스케쥴 태그) + public List scheduleTagRegistReqDTOList; + // 계획 리스트 private List planRegistReqDTOList; } diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java index 9ba74c1..5ceb97e 100644 --- a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java @@ -14,6 +14,7 @@ @Builder(toBuilder = true) @Schema(description = "일정 등록 응답 DTO") public class ScheduleRegistResDTO { + private Integer scheduleNum; // 일정 번호 private String scheduleNm; // 일정명 private String startDate; // 시작 날짜 private String endDate; // 종료 날짜 diff --git a/src/main/java/com/barogagi/tag/command/entity/PlanTag.java b/src/main/java/com/barogagi/tag/command/entity/PlanTag.java new file mode 100644 index 0000000..61c9e1a --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/PlanTag.java @@ -0,0 +1,28 @@ +package com.barogagi.tag.command.entity; + +import com.barogagi.plan.command.entity.Plan; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Table(name = "PLAN_TAG") +public class PlanTag { + + @EmbeddedId + private PlanTagId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("planNum") + @JoinColumn(name = "PLAN_NUM") + private Plan plan; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("tagNum") + @JoinColumn(name = "TAG_NUM") + private Tag tag; +} + diff --git a/src/main/java/com/barogagi/tag/command/entity/PlanTagId.java b/src/main/java/com/barogagi/tag/command/entity/PlanTagId.java new file mode 100644 index 0000000..1f5fdf4 --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/PlanTagId.java @@ -0,0 +1,19 @@ +package com.barogagi.tag.command.entity; + +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@Embeddable +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PlanTagId implements Serializable { + private Integer tagNum; + private Integer planNum; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java new file mode 100644 index 0000000..eb2c120 --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java @@ -0,0 +1,29 @@ +package com.barogagi.tag.command.entity; + +import com.barogagi.schedule.command.entity.Schedule; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Table(name = "SCHEDULE_TAG") +public class ScheduleTag { + + @EmbeddedId + private ScheduleTagId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("scheduleNum") + @JoinColumn(name = "SCHEDULE_NUM") + private Schedule schedule; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("tagNum") + @JoinColumn(name = "TAG_NUM") + private Tag tag; +} + + diff --git a/src/main/java/com/barogagi/tag/command/entity/ScheduleTagId.java b/src/main/java/com/barogagi/tag/command/entity/ScheduleTagId.java new file mode 100644 index 0000000..94efa0b --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/ScheduleTagId.java @@ -0,0 +1,19 @@ +package com.barogagi.tag.command.entity; + +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@Embeddable +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleTagId implements Serializable { + private Integer tagNum; + private Integer scheduleNum; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/command/entity/Tag.java b/src/main/java/com/barogagi/tag/command/entity/Tag.java new file mode 100644 index 0000000..72ecd43 --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/Tag.java @@ -0,0 +1,33 @@ +package com.barogagi.tag.command.entity; + + +import com.barogagi.plan.command.entity.Category; +import com.barogagi.tag.enums.TagType; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Table(name = "TAG") +@ToString(exclude = "category") +public class Tag { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "TAG_NUM") + private Integer tagNum; + + @Column(name = "TAG_NM", nullable = false, length = 100) + private String tagNm; + + @Enumerated(EnumType.STRING) + @Column(name = "TAG_TYPE", nullable = false, length = 1) + private TagType tagType; // ENUM('P', 'S') + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CATEGORY_NUM") + private Category category; +} diff --git a/src/main/java/com/barogagi/tag/command/repository/ScheduleTagRepository.java b/src/main/java/com/barogagi/tag/command/repository/ScheduleTagRepository.java new file mode 100644 index 0000000..187258a --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/repository/ScheduleTagRepository.java @@ -0,0 +1,11 @@ +package com.barogagi.tag.command.repository; + +import com.barogagi.schedule.command.entity.Schedule; +import com.barogagi.tag.command.entity.ScheduleTag; +import com.barogagi.tag.command.entity.ScheduleTagId; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ScheduleTagRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/command/repository/TagRepository.java b/src/main/java/com/barogagi/tag/command/repository/TagRepository.java new file mode 100644 index 0000000..c931a22 --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/repository/TagRepository.java @@ -0,0 +1,12 @@ +package com.barogagi.tag.command.repository; +import com.barogagi.tag.command.entity.Tag; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface TagRepository extends JpaRepository { +// Optional findById(Integer tagNum); +} + diff --git a/src/main/resources/mapper/ItemMapper.xml b/src/main/resources/mapper/ItemMapper.xml index 5307fd8..88746f5 100644 --- a/src/main/resources/mapper/ItemMapper.xml +++ b/src/main/resources/mapper/ItemMapper.xml @@ -6,5 +6,12 @@ - + From 5333d21526e07bcd3695f4e65f418e5818d7730e Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sat, 11 Oct 2025 14:11:26 +0900 Subject: [PATCH 19/79] =?UTF-8?q?feat:=20=EC=83=9D=EC=84=B1=EB=90=9C=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=EC=9D=84=20DB=EC=97=90=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EB=8A=94=20API=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../barogagi/plan/dto/PlanRegistResDTO.java | 8 +- .../service/ScheduleCommandService.java | 205 ++++++++++-------- .../controller/ScheduleController.java | 106 ++++++++- .../schedule/dto/ScheduleRegistResDTO.java | 5 + 4 files changed, 231 insertions(+), 93 deletions(-) diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java index 15d22eb..69cf683 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java @@ -50,15 +50,13 @@ public class PlanRegistResDTO { @Schema(description = "장소 주소") public String planAddress; - @Schema(description = "지역 정보 DTO") + @Schema(description = "지역명") public String regionNm; @Schema(description = "지역 번호") public Integer regionNum; - @Schema(description = "태그 목록") - public List tagRegistResDTOList; - - // private KakaoPlaceResDTO aiChosen; + @Schema(description = "계획 태그 목록") + public List planTagRegistResDTOList; } diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 8390fd0..2b69fae 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -10,6 +10,7 @@ import com.barogagi.naverblog.dto.NaverBlogResDTO; import com.barogagi.plan.command.entity.Item; import com.barogagi.plan.command.entity.Plan; +import com.barogagi.plan.command.ex_entity.PlanUserMembershipInfo; import com.barogagi.plan.command.repository.ItemRepository; import com.barogagi.plan.command.repository.PlanRepository; import com.barogagi.plan.command.repository.PlanTagRepository; @@ -279,6 +280,8 @@ else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty() } PlanRegistResDTO planRes = PlanRegistResDTO.builder() + .startTime(plan.getStartTime()) + .endTime(plan.getEndTime()) .planNm(aiChosen.getPlaceName()) .planLink(aiChosen.getPlaceUrl()) .planDescription(aiRes != null ? aiRes.getAiDescription() : null) @@ -289,7 +292,7 @@ else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty() .categoryNum(plan.getCategoryNum()) .itemNm(itemNm) .itemNum(plan.getItemNum()) - .tagRegistResDTOList( + .planTagRegistResDTOList( Optional.ofNullable(plan.getPlanTagRegistReqDTOList()) .orElseGet(List::of) .stream() @@ -318,115 +321,147 @@ else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty() .build(); } -// @Transactional - @Transactional(propagation = Propagation.REQUIRES_NEW) - public Schedule registScheduleInfo(ScheduleRegistReqDTO scheduleRegistReqDTO, List planResList) { - logger.info("#$# START DB SAVE!"); - - // (1) Schedule - Schedule schedule = Schedule.builder() - .membershipNo(1) - .scheduleNm(scheduleRegistReqDTO.getScheduleNm()) - .startDate(scheduleRegistReqDTO.getStartDate()) - .endDate(scheduleRegistReqDTO.getEndDate()) - .radius(radius) - .build(); - - scheduleRepository.save(schedule); - logger.info("#$# schedule Save! scheduleNum={}", schedule.getScheduleNum()); - - // (2) Schedule_tag - if (scheduleRegistReqDTO.getScheduleTagRegistReqDTOList() != null) { - for (TagRegistReqDTO tagReq : scheduleRegistReqDTO.getScheduleTagRegistReqDTOList()) { - Tag tag = tagRepository.findById(tagReq.getTagNum()) - .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagReq.getTagNum())); - scheduleTagRepository.save( - ScheduleTag.builder() - .id(new ScheduleTagId(tag.getTagNum(), schedule.getScheduleNum())) - .schedule(schedule) - .tag(tag) - .build() - ); - } - } - logger.info("#$# scheduleTag Save! scheduleNum={}", schedule.getScheduleNum()); - // (3) Plan + Plan_tag + Plan_region + Place - for (int i = 0; i < scheduleRegistReqDTO.getPlanRegistReqDTOList().size(); i++) { + // 등록완료된 스케쥴의 num을 리턴 + public Integer registSchedule(ScheduleRegistResDTO scheduleRegistResDTO) { + return registScheduleInfo(scheduleRegistResDTO); + } - PlanRegistReqDTO planReq = scheduleRegistReqDTO.getPlanRegistReqDTOList().get(i); - PlanRegistResDTO planRes = planResList.get(i); // AI 결과 매칭 - logger.info("#$# planRes={}", planRes); - Item item = itemRepository.findById(planReq.getItemNum()) - .orElseThrow(() -> new IllegalArgumentException("Item not found: " + planReq.getItemNum())); - logger.info("#$# item={}", item); - // ① Plan 저장 - Plan plan = Plan.builder() - .startTime(planReq.getStartTime()) - .endTime(planReq.getEndTime()) - .schedule(schedule) - .item(item) +// @Transactional + @Transactional(propagation = Propagation.REQUIRES_NEW) + public Integer registScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ + logger.info("START DB SAVE!"); + try { + + // 1. Schedule + Schedule schedule = Schedule.builder() + .membershipNo(1) // todo. token에서 정보 가져오는 방식으로 수정 필요 + .scheduleNm(scheduleRegistResDTO.getScheduleNm()) + .startDate(scheduleRegistResDTO.getStartDate()) + .endDate(scheduleRegistResDTO.getEndDate()) + .radius(radius) .build(); - planRepository.saveAndFlush(plan); - logger.info("✅ Plan saved: planNum={}", plan.getPlanNum()); - + scheduleRepository.save(schedule); + logger.info("schedule Save! scheduleNum={}", schedule.getScheduleNum()); - // ② Plan_tag - if (planReq.getPlanTagRegistReqDTOList() != null) { - for (TagRegistReqDTO tagReq : planReq.getPlanTagRegistReqDTOList()) { + // 2. Schedule_tag + if (scheduleRegistResDTO.getScheduleTagRegistResDTOList() != null) { + for (TagRegistResDTO tagReq : scheduleRegistResDTO.getScheduleTagRegistResDTOList()) { Tag tag = tagRepository.findById(tagReq.getTagNum()) .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagReq.getTagNum())); - planTagRepository.save( - PlanTag.builder() - .id(new PlanTagId(tag.getTagNum(), plan.getPlanNum())) - .plan(plan) + scheduleTagRepository.save( + ScheduleTag.builder() + .id(new ScheduleTagId(tag.getTagNum(), schedule.getScheduleNum())) + .schedule(schedule) .tag(tag) .build() ); } } + logger.info("scheduleTag Save! scheduleNum={}", schedule.getScheduleNum()); - // ③ Plan_region - if (planReq.getRegionRegistReqDTOList() != null) { - for (RegionRegistReqDTO regionReq : planReq.getRegionRegistReqDTOList()) { - Region region = regionRepository.findById(regionReq.getRegionNum()) - .orElseThrow(() -> new IllegalArgumentException("Region not found: " + regionReq.getRegionNum())); - - planRegionRepository.save( - PlanRegion.builder() - .id(new PlanRegionId(region.getRegionNum(), plan.getPlanNum())) - .plan(plan) - .region(region) - .build() - ); - } - } + // 3. Plan + Plan_tag + Plan_region + Place + for (int i = 0; i < scheduleRegistResDTO.getPlanRegistResDTOList().size(); i++) { + + PlanRegistResDTO planRes = scheduleRegistResDTO.getPlanRegistResDTOList().get(i); - // ④ Place - if (planRes.getRegionNum() != null) { - Region region = regionRepository.findById(planRes.getRegionNum()) - .orElseThrow(() -> new IllegalArgumentException("Region not found: " + planRes.getRegionNum())); + Item item = itemRepository.findById(planRes.getItemNum()) + .orElseThrow(() -> new IllegalArgumentException("Item not found: " + planRes.getItemNum())); + + PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() + .membershipNo(1) // todo. token에서 정보 가져오는 방식으로 수정 필요 + .build(); - Place place = Place.builder() - .region(region) - .regionNm(region.getRegionLevel3() != null ? region.getRegionLevel3() : region.getRegionLevel2()) - .address(planRes.getPlanAddress()) + // 3-1. Plan + Plan plan = Plan.builder() + .planNum(planRes.getPlanNum()) + .planNm(planRes.getPlanNm()) + .startTime(planRes.getStartTime()) + .endTime(planRes.getEndTime()) .planLink(planRes.getPlanLink()) - .placeDescription(planRes.getPlanDescription()) - .plan(plan) + .planDescription(planRes.getPlanDescription()) + .planAddress(planRes.getPlanAddress()) + .schedule(schedule) + .user(user) + .item(item) .build(); - placeRepository.save(place); - logger.info("✅ Place saved for planNum={}, regionNum={}", plan.getPlanNum(), planRes.getRegionNum()); + planRepository.saveAndFlush(plan); + logger.info("Plan save! planNum={}", plan.getPlanNum()); + + + // 3-2. Plan_tag + if (planRes.getPlanTagRegistResDTOList() != null) { + + for (TagRegistResDTO tagRes : planRes.getPlanTagRegistResDTOList()) { + Tag tag = tagRepository.findById(tagRes.getTagNum()) + .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagRes.getTagNum())); + + PlanTag planTag = PlanTag.builder() + .id(new PlanTagId(tag.getTagNum(), plan.getPlanNum())) + .plan(plan) + .tag(tag) + .build(); + + planTagRepository.save(planTag); + } + } + + // 3-3. Plan_region + if (planRes.getRegionNum() != null) { + Region region = regionRepository.findById(planRes.getRegionNum()) + .orElseThrow(() -> new IllegalArgumentException("Region not found: " + planRes.getRegionNum())); + + PlanRegionId planRegionId = new PlanRegionId(plan.getPlanNum(), region.getRegionNum()); + + PlanRegion planRegion = PlanRegion.builder() + .id(planRegionId) + .region(region) + .plan(plan) + .build(); + + planRegionRepository.save(planRegion); + logger.info("PlanRegion save! regionNum={}, planNum={}", region.getRegionNum(), plan.getPlanNum()); + + } + + // 3-4. Place + if (planRes.getRegionNum() != null) { + Region region = regionRepository.findById(planRes.getRegionNum()) + .orElseThrow(() -> new IllegalArgumentException("Region not found: " + planRes.getRegionNum())); + + Place place = Place.builder() + .region(region) + .regionNm(region.getRegionLevel3() != null ? region.getRegionLevel3() : region.getRegionLevel2()) + .address(planRes.getPlanAddress()) + .planLink(planRes.getPlanLink()) + .placeDescription(planRes.getPlanDescription()) + .plan(plan) + .build(); + + placeRepository.save(place); + logger.info("Place save! planNum={}, regionNum={}", plan.getPlanNum(), planRes.getRegionNum()); + + // 3-5. plan-place 동기화 + plan.toBuilder().place(place).build(); + } + } + logger.info("END DB SAVE!"); + + return schedule.getScheduleNum(); + + } catch (Exception e) { + logger.error(e.getMessage()); } - return schedule; + return null; + } diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 8d5d78b..05796a5 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -111,8 +111,11 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 } - @Operation(summary = "일정 생성 기능", description = "일정을 생성하는 기능입니다.") - @PostMapping("") + @Operation(summary = "일정 생성 기능", + description = "일정을 생성하는 기능입니다.
" + + "- 생성된 일정은 '일정 등록'과정을 거쳐야 DB에 저장됩니다.
" + + "- 사용자가 이 API로 생성된 일정을 확인한 후 '일정 생성하기' 버튼을 누르면 '일정 등록' API를 호출해 주세요.") + @PostMapping("/create") public ApiResponse createSchedule( @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "일정 등록 요청", @@ -185,7 +188,7 @@ public ApiResponse createSchedule( @RequestBody ScheduleRegistReqDTO scheduleRegistReqDTO ) { - logger.info("CALL /schedule"); + logger.info("CALL /schedule/create"); logger.info("[input] scheduleRegistReqDTO={}", scheduleRegistReqDTO); ScheduleRegistResDTO result; @@ -200,4 +203,101 @@ public ApiResponse createSchedule( return ApiResponse.success(result, "일정 생성 성공"); } + + + @Operation(summary = "일정 등록 기능", + description = "일정을 등록(DB에 저장)하는 기능입니다.
" + + "- '일정 생성하기' 버튼을 눌렀을 때 호출되는 API입니다.
" + + "- '일정 생성' API로 받은 응답 DTO를 그대로 보내주세요.") + @PostMapping("") + public ApiResponse registSchedule( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "일정 등록 요청", + required = true, + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "일정 등록 요청 예시", + value = "{\n" + + " \"scheduleNum\": null,\n" + + " \"scheduleNm\": \"서울 데이트 코스\",\n" + + " \"startDate\": \"2025-07-01\",\n" + + " \"endDate\": \"2025-07-01\",\n" + + " \"planRegistResDTOList\": [\n" + + " {\n" + + " \"startTime\": \"08:30\",\n" + + " \"endTime\": \"09:00\",\n" + + " \"itemNum\": 10,\n" + + " \"itemNm\": \"프랜차이즈카페\",\n" + + " \"categoryNum\": 2,\n" + + " \"categoryNm\": \"카페\",\n" + + " \"planNm\": \"제비꽃다방\",\n" + + " \"planLink\": \"http://place.map.kakao.com/24944966\",\n" + + " \"planDescription\": \"분위기 좋은 한옥 카페 '더숲 초소책방'은 서울 종로구에 위치해 있으며, 숲속의 아늑함을 느낄 수 있는 넓은 야외 공간과 아름다운 서울 풍경을 감상할 수 있는 2층 테라스가 특징입니다.\",\n" + + " \"planAddress\": \"서울 종로구 창의문로 146\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 14, \"tagNm\": \"디저트맛집\" },\n" + + " { \"tagNum\": 15, \"tagNm\": \"인스타핫플\" }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"startTime\": \"14:00\",\n" + + " \"endTime\": \"15:00\",\n" + + " \"itemNum\": 2,\n" + + " \"itemNm\": \"한식\",\n" + + " \"categoryNum\": 1,\n" + + " \"categoryNm\": \"식사\",\n" + + " \"planNm\": \"식사\",\n" + + " \"planLink\": \"http://place.map.kakao.com/1581311090\",\n" + + " \"planDescription\": \"분위기 좋은 카페로 뷰가 좋은 곳입니다.\",\n" + + " \"planAddress\": \"서울 중구 무교로 17\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 17, \"tagNm\": \"조용한\" }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"startTime\": \"15:30\",\n" + + " \"endTime\": \"19:00\",\n" + + " \"itemNum\": 15,\n" + + " \"itemNm\": \"놀이공원\",\n" + + " \"categoryNum\": 4,\n" + + " \"categoryNm\": \"놀거리\",\n" + + " \"planNm\": \"구룡관 혜화본점\",\n" + + " \"planLink\": \"http://place.map.kakao.com/40669117\",\n" + + " \"planDescription\": \"혜화에서 분위기 좋고 저렴한 중식 술집으로는 구룡관 혜화본점이 추천됩니다.\",\n" + + " \"planAddress\": \"서울 종로구 창경궁로 258-5\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 4, \"tagNm\": \"테마파크\" }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}" + ) + ) + ) + @RequestBody ScheduleRegistResDTO scheduleRegistResDTO + ) { + + logger.info("CALL /schedule"); + logger.info("[input] scheduleRegistResDTO={}", scheduleRegistResDTO); + + int result; + try { + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ + result = scheduleCommandService.registSchedule(scheduleRegistResDTO); + + } catch (Exception e) { + return ApiResponse.error("404", "일정 생성 실패"); + } + + + return ApiResponse.success(result, "일정 생성 성공"); + } + } diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java index 5ceb97e..6fc42bc 100644 --- a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java @@ -2,6 +2,8 @@ import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.dto.PlanRegistResDTO; +import com.barogagi.tag.dto.TagRegistReqDTO; +import com.barogagi.tag.dto.TagRegistResDTO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; @@ -19,6 +21,9 @@ public class ScheduleRegistResDTO { private String startDate; // 시작 날짜 private String endDate; // 종료 날짜 + // 일정 태그 목록 (스케쥴 태그) + public List scheduleTagRegistResDTOList; + // 계획 리스트 private List planRegistResDTOList; } From eb5249a6bf88092e12acbe35fae8c5b6d2880496 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sat, 11 Oct 2025 14:26:26 +0900 Subject: [PATCH 20/79] =?UTF-8?q?update:=20=EC=9D=BC=EC=A0=95=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20API=EC=9D=98=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=A1=B0=ED=9A=8C=20=EC=BF=BC=EB=A6=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plan/query/service/PlanQueryService.java | 5 +- .../controller/ScheduleController.java | 50 ++++--------------- src/main/resources/mapper/TagMapper.xml | 4 +- 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java b/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java index 8af5073..7ec6e0c 100644 --- a/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java +++ b/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java @@ -21,9 +21,6 @@ public PlanQueryService (PlanMapper planMapper) { } public List getPlanDetail(int scheduleNum) throws Exception{ - logger.info("scheduleNum={}", scheduleNum); - List result = planMapper.selectPlanDetailByScheduleNum(scheduleNum); - logger.info("result={}", result); - return result; + return planMapper.selectPlanDetailByScheduleNum(scheduleNum); } } diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 05796a5..ed504dd 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -60,54 +60,22 @@ public ScheduleController(Environment environment, public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 번호", example = "1") @RequestParam Integer scheduleNum) { - logger.info("CALL /schedule/detail"); - logger.info("[input] SchedulNm={}", scheduleNum); - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; + logger.info("CALL /schedule/detail"); + logger.info("[input] scheduleNum={}", scheduleNum); + ScheduleDetailResDTO result; try { - + // TODO. 해당 사용자의 일정이 맞는지도 체크해야 함 //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ - if (true) { - - // TODO. 해당 사용자의 일정이 맞는지도 체크해야 함 - if (inputValidate.isInvalidInteger(scheduleNum)) { - resultCode = "101"; - message = "조회할 일정을 선택해주세요."; - } else { - - ScheduleDetailResDTO result = scheduleQueryService.getScheduleDetail(scheduleNum); - logger.info("result={}", result.toString()); + result = scheduleQueryService.getScheduleDetail(scheduleNum); - if (result == null) { - resultCode = "300"; - message = "조회할 일정이 존재하지 않습니다."; // TODO. 에러 메시지 정의하기 - - } else { - resultCode = "200"; - message = "일정 상세 조회 성공"; - apiResponse.setData(result); - logger.info("#$# result={}", result.toString()); - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - logger.info("#$# 11 resultCode={}", resultCode); } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); + return ApiResponse.error("404", "일정 조회 실패"); } - return apiResponse; + + + return ApiResponse.success(result, "일정 조회 성공"); } diff --git a/src/main/resources/mapper/TagMapper.xml b/src/main/resources/mapper/TagMapper.xml index f68692c..0914f7d 100644 --- a/src/main/resources/mapper/TagMapper.xml +++ b/src/main/resources/mapper/TagMapper.xml @@ -11,6 +11,8 @@ SELECT b.TAG_NUM as tagNum , b.TAG_NM as tagNm + , b.TAG_TYPE as tagType + , b.CATEGORY_NUM as categoryNum FROM PLAN_TAG a JOIN TAG b ON a.TAG_NUM = b.TAG_NUM WHERE a.PLAN_NUM = #{planNum}; @@ -20,7 +22,7 @@ + + + From a3426974506c7e11d5ebaa4fa648cf1f234d1838 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sun, 23 Nov 2025 20:12:39 +0900 Subject: [PATCH 28/79] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=A7=81=EC=A0=91=20=EC=B6=94=EA=B0=80=ED=95=9C=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95,=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20API?= =?UTF-8?q?=EB=A1=9C=20=EA=B2=80=EC=83=89=ED=95=9C=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kakaoplace/client/KakaoPlaceClient.java | 1 + .../plan/controller/PlaceController.java | 1 + .../barogagi/plan/dto/PlanRegistReqDTO.java | 17 +- .../barogagi/plan/dto/UserAddedPlaceDTO.java | 14 + .../region/query/mapper/RegionMapper.java | 10 + .../query/service/RegionQueryService.java | 68 +++- .../service/ScheduleCommandService.java | 383 +++++++++++------- .../controller/ScheduleController.java | 49 ++- src/main/resources/mapper/RegionMapper.xml | 47 +++ 9 files changed, 404 insertions(+), 186 deletions(-) create mode 100644 src/main/java/com/barogagi/plan/dto/UserAddedPlaceDTO.java diff --git a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java index 0fd05c1..559c38c 100644 --- a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java +++ b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java @@ -72,4 +72,5 @@ public List searchKakaoPlaceByKeyword(String query) { List body = response.getBody().getDocuments(); return body; } + } diff --git a/src/main/java/com/barogagi/plan/controller/PlaceController.java b/src/main/java/com/barogagi/plan/controller/PlaceController.java index 8302e5a..232f412 100644 --- a/src/main/java/com/barogagi/plan/controller/PlaceController.java +++ b/src/main/java/com/barogagi/plan/controller/PlaceController.java @@ -32,6 +32,7 @@ public PlaceController(Environment environment, @Operation(summary = "장소 검색 기능", description = "사용자가 찾고 싶은 장소를 Kakao API로 검색하는 기능입니다.
" + + "- 일정을 생성하는 API를 호출할 때, 이 API에서 받은 placeName, placeUrl, addressName을 보내주세요.
" + "- regionNum은 쓰지 않는 필드입니다. (null 값이 전달됨)") @GetMapping("/keyword-search") public ApiResponse searchPlace(@Parameter(description = "검색 키워드", example = "스타벅스") diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java index 2a50b61..d26b90b 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java @@ -15,9 +15,6 @@ @Schema(description = "계획 등록 요청 DTO") public class PlanRegistReqDTO { - //@Schema(description = "계획 이름", example = "프랜차이즈카페") - //public String planNm; - @Schema(description = "시작 시간", example = "08:00") public String startTime; @@ -35,5 +32,19 @@ public class PlanRegistReqDTO { @Schema(description = "계획 태그 목록") public List planTagRegistReqDTOList; + + // 사용자가 직접 세부일정을 추가한 경우에만 필요한 값 + @Schema(description = "사용자가 수동으로 추가한 일정인지 여부(AI 생성 안함)", example = "Y") + public String isUserAdded; + + @Schema(description = "사용자 직접 추가 CASE 1. 카카오 API 장소검색으로 추가한 장소 정보.") + UserAddedPlaceDTO userAddedPlaceDTO; + + @Schema(description = "사용자 직접 추가 CASE 2. 사용자가 직접 입력한 장소", example = "친구집 방문") + public String planNm; + + + + } diff --git a/src/main/java/com/barogagi/plan/dto/UserAddedPlaceDTO.java b/src/main/java/com/barogagi/plan/dto/UserAddedPlaceDTO.java new file mode 100644 index 0000000..3f43843 --- /dev/null +++ b/src/main/java/com/barogagi/plan/dto/UserAddedPlaceDTO.java @@ -0,0 +1,14 @@ +package com.barogagi.plan.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@Schema(description = "계획에 사용자가 수동으로 추가한 장소 정보 DTO") +public class UserAddedPlaceDTO { + private String placeName; + private String placeUrl; + private String addressName; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java b/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java index 638e3ee..f714a7b 100644 --- a/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java +++ b/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java @@ -17,4 +17,14 @@ public interface RegionMapper { // 키워드로 지역 목록 검색 - level 1부터 가장 정확도 높은 순으로 10개 리턴 List selectRegionByRegionNm(String regionQuery); + // 지역 번호로 지역명 조회 (단순 String 반환) +// String selectRegionNameByRegionNum(int regionNum); + + RegionDetailVO selectRegionByLevel4(String level4); + + RegionDetailVO selectRegionByLevel3(String level3); + + RegionDetailVO selectRegionByLevel2(String level2); + + RegionDetailVO selectRegionByLevel1(String level1); } diff --git a/src/main/java/com/barogagi/region/query/service/RegionQueryService.java b/src/main/java/com/barogagi/region/query/service/RegionQueryService.java index 60c22be..ee64460 100644 --- a/src/main/java/com/barogagi/region/query/service/RegionQueryService.java +++ b/src/main/java/com/barogagi/region/query/service/RegionQueryService.java @@ -1,20 +1,19 @@ package com.barogagi.region.query.service; -import com.barogagi.kakaoplace.client.KakaoGeoCodeClient; -import com.barogagi.kakaoplace.dto.KakaoGeoCodeResDTO; -import com.barogagi.region.dto.RegionGeoCodeResDTO; import com.barogagi.region.dto.RegionSearchResDTO; import com.barogagi.region.query.mapper.RegionMapper; import com.barogagi.region.query.vo.RegionDetailVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; import java.util.stream.Collectors; -import java.util.stream.Stream; @Service public class RegionQueryService { + private static final Logger logger = LoggerFactory.getLogger(RegionQueryService.class); private final RegionMapper regionMapper; @@ -23,7 +22,6 @@ public RegionQueryService(RegionMapper regionMapper) { this.regionMapper = regionMapper; } - /** * 검색어로 지역 정보를 조회하고, * 각 지역의 행정구역 단계별 주소를 RegionSearchResDTO 리스트로 반환한다. @@ -88,6 +86,66 @@ public List searchList(String regionQuery) { return result; } + public RegionDetailVO getRegionByRegionNum(int regionNum) { + return regionMapper.selectRegionByRegionNum(regionNum); + } + + public RegionDetailVO getRegionNumByAddress(String address) { + + // todo. !!!!!!!!!! 주소가 이상하게 들어감 !!!!!!!! + if (address == null || address.isBlank()) { + throw new IllegalArgumentException("주소가 입력되지 않았습니다."); + } + + String[] parts = address.split(" "); + + List tokens = Arrays.stream(parts) + .filter(t -> !t.matches("\\d+")) + .collect(Collectors.toList()); + + // LEVEL 4 (동/읍/면) + if (tokens.size() >= 3) { + String level4 = tokens.get(2); + RegionDetailVO r4 = regionMapper.selectRegionByLevel4(level4); + logger.info("#$# r4 = {}", r4); + + if (r4 != null) { + return r4; + } + } + logger.info("#$# next?"); + + // LEVEL 3 + if (tokens.size() >= 3) { + String level3 = tokens.get(2); + RegionDetailVO r3 = regionMapper.selectRegionByLevel3(level3); + logger.info("#$# r3 = {}", r3); + + if (r3 != null) { + return r3; + } + } + + // LEVEL 2 + if (tokens.size() >= 2) { + String level2 = tokens.get(1); + RegionDetailVO r2 = regionMapper.selectRegionByLevel2(level2); + if (r2 != null) { + return r2; + } + } + + // LEVEL 1 + if (tokens.size() >= 1) { + String level1 = tokens.get(0); + RegionDetailVO r1 = regionMapper.selectRegionByLevel1(level1); + if (r1 != null) { + return r1; + } + } + + throw new IllegalStateException("입력된 주소로 지역을 찾을 수 없습니다: " + address); + } } diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 37e9e51..bbd5960 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -16,6 +16,7 @@ import com.barogagi.plan.command.repository.PlanTagRepository; import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.dto.PlanRegistResDTO; +import com.barogagi.plan.dto.UserAddedPlaceDTO; import com.barogagi.plan.query.mapper.CategoryMapper; import com.barogagi.plan.query.mapper.ItemMapper; import com.barogagi.region.command.entity.Place; @@ -27,8 +28,10 @@ import com.barogagi.region.command.repository.RegionRepository; import com.barogagi.region.dto.RegionGeoCodeResDTO; import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.region.dto.RegionSearchResDTO; import com.barogagi.region.query.service.RegionGeoCodeService; import com.barogagi.region.query.service.RegionQueryService; +import com.barogagi.region.query.vo.RegionDetailVO; import com.barogagi.schedule.command.entity.Schedule; import com.barogagi.schedule.command.repository.ScheduleRepository; import com.barogagi.schedule.dto.ScheduleRegistReqDTO; @@ -75,6 +78,7 @@ public class ScheduleCommandService { private final RegionRepository regionRepository; private final PlanRegionRepository planRegionRepository; private final PlaceRepository placeRepository; + private final RegionQueryService regionQueryService ; @Value("${kakao.radius}") @@ -91,7 +95,7 @@ public ScheduleCommandService(CategoryMapper categoryMapper, ItemMapper itemMapp TagRepository tagRepository, ItemRepository itemRepository, PlanRepository planRepository, PlanTagRepository planTagRepository, RegionRepository regionRepository, PlanRegionRepository planRegionRepository, - PlaceRepository placeRepository) { + PlaceRepository placeRepository, RegionQueryService regionQueryService) { this.itemMapper = itemMapper; this.categoryMapper = categoryMapper; this.kakaoPlaceClient = kakaoPlaceClient; @@ -108,8 +112,11 @@ public ScheduleCommandService(CategoryMapper categoryMapper, ItemMapper itemMapp this.regionRepository = regionRepository; this.planRegionRepository = planRegionRepository; this.placeRepository = placeRepository; + this.regionQueryService = regionQueryService; } + + public ScheduleRegistResDTO createSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { List planResList = new ArrayList<>(); @@ -131,198 +138,260 @@ public ScheduleRegistResDTO createSchedule(ScheduleRegistReqDTO scheduleRegistRe }); for (PlanRegistReqDTO plan : scheduleRegistReqDTO.getPlanRegistReqDTOList()) { - // int radius = // scheduleRegistReqDTO.getRadius(); - // ---------- 1) 지역 번호로 x, y 좌표 검색 & Kakao 후보장소 수집(평탄화) ---------- - if (plan.getRegionRegistReqDTOList() == null || plan.getRegionRegistReqDTOList().isEmpty()) { - logger.info("#$# skip: plan has no regions. plan={}", plan); - continue; + if (plan.getIsUserAdded().equals("Y")) { + // ➜ A. 사용자가 직접 입력한 플랜 + logger.info("#$# ➜ A. 사용자가 직접 입력한 플랜 plan={}", plan); + PlanRegistResDTO planRes = handleUserPlan(plan); + planResList.add(planRes); + + } else { + // ➜ B. AI가 추천해줘야 하는 플랜 + logger.info("#$# ➜ B. AI가 추천해줘야 하는 플랜 plan={}", plan); + PlanRegistResDTO planRes = handleAIPlan(scheduleRegistReqDTO, plan); + planResList.add(planRes); } + logger.info("#$# for문 도는중 planResList={}", planResList); - int limitPlace = calLimitPlace(plan.getRegionRegistReqDTOList().size()); - List> allKakaoPlaceResults = new ArrayList<>(); + } - String categoryNm = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); - String queryString = categoryNm; // 검색어 + // ---------- 6) ScheduleRegistResDTO 묶어서 리턴 ---------- + return ScheduleRegistResDTO.builder() + .scheduleNm(scheduleNm) + .startDate(startDate) + .endDate(endDate) + .planRegistResDTOList(planResList) + .build(); + } - String itemNm = itemMapper.selectItemNmBy(plan.getItemNum()); // todo. itemNm을 검색어로 쓸지 고려하기 + private PlanRegistResDTO handleAIPlan(ScheduleRegistReqDTO scheduleRegistReqDTO, PlanRegistReqDTO plan) { + // ---------- 1) 지역 번호로 x, y 좌표 검색 & Kakao 후보장소 수집(평탄화) ---------- + if (plan.getRegionRegistReqDTOList() == null || plan.getRegionRegistReqDTOList().isEmpty()) { + logger.info("#$# skip: plan has no regions. plan={}", plan); + return null; + } - for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { - // regionNum으로 좌표 가져오기 - RegionGeoCodeResDTO geo = regionGeoCodeService.getGeocode(region.getRegionNum()); - if (geo == null) { - logger.warn("#$# regionNum={} not found in DB, skip.", region.getRegionNum()); - continue; - } + int limitPlace = calLimitPlace(plan.getRegionRegistReqDTOList().size()); - RegionRegistReqDTO updatedRegion = region.toBuilder() - .regionLevel1(geo.getRegionLevel1()) - .regionLevel2(geo.getRegionLevel2()) - .regionLevel3(geo.getRegionLevel3()) - .regionLevel4(geo.getRegionLevel4()) - .build(); + List> allKakaoPlaceResults = new ArrayList<>(); - // 리스트 교체 - int idx = plan.getRegionRegistReqDTOList().indexOf(region); - plan.getRegionRegistReqDTOList().set(idx, updatedRegion); + String categoryNm = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); + String queryString = categoryNm; // 검색어 - // 지역명 결정 (레벨2/3 우선순위 적용) - String regionName = null; - if (updatedRegion.getRegionLevel3() != null && !updatedRegion.getRegionLevel3().isEmpty()) { - regionName = updatedRegion.getRegionLevel3(); - } else if (updatedRegion.getRegionLevel2() != null && !updatedRegion.getRegionLevel2().isEmpty()) { - regionName = updatedRegion.getRegionLevel2(); - } + String itemNm = itemMapper.selectItemNmBy(plan.getItemNum()); // todo. itemNm을 검색어로 쓸지 고려하기 - List oneRegionPlaces = - kakaoPlaceClient.searchKakaoPlace(queryString, geo.getX(), geo.getY(), radius, limitPlace); - allKakaoPlaceResults.add(oneRegionPlaces); + for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { + // regionNum으로 좌표 가져오기 + RegionGeoCodeResDTO geo = regionGeoCodeService.getGeocode(region.getRegionNum()); + if (geo == null) { + logger.warn("#$# regionNum={} not found in DB, skip.", region.getRegionNum()); + continue; + } - // 각 장소에 regionNum 세팅 - if (oneRegionPlaces != null) { - oneRegionPlaces.forEach(k -> k.setRegionNum(region.getRegionNum())); - } + RegionRegistReqDTO updatedRegion = region.toBuilder() + .regionLevel1(geo.getRegionLevel1()) + .regionLevel2(geo.getRegionLevel2()) + .regionLevel3(geo.getRegionLevel3()) + .regionLevel4(geo.getRegionLevel4()) + .build(); - logger.info("#$# resolved regionName={} for regionNum={}", regionName, updatedRegion.getRegionNum()); + // 리스트 교체 + int idx = plan.getRegionRegistReqDTOList().indexOf(region); + plan.getRegionRegistReqDTOList().set(idx, updatedRegion); + // 지역명 결정 (레벨2/3 우선순위 적용) + String regionName = null; + if (updatedRegion.getRegionLevel3() != null && !updatedRegion.getRegionLevel3().isEmpty()) { + regionName = updatedRegion.getRegionLevel3(); + } else if (updatedRegion.getRegionLevel2() != null && !updatedRegion.getRegionLevel2().isEmpty()) { + regionName = updatedRegion.getRegionLevel2(); } - logger.info("allKakaoPlaceResults: {}", allKakaoPlaceResults); + List oneRegionPlaces = + kakaoPlaceClient.searchKakaoPlace(queryString, geo.getX(), geo.getY(), radius, limitPlace); + allKakaoPlaceResults.add(oneRegionPlaces); + // 각 장소에 regionNum 세팅 + if (oneRegionPlaces != null) { + oneRegionPlaces.forEach(k -> k.setRegionNum(region.getRegionNum())); + } + logger.info("#$# resolved regionName={} for regionNum={}", regionName, updatedRegion.getRegionNum()); + } - // Kakao 평탄화(이 순서를 기준으로 이후 Naver/AI도 동일하게 맞춤) - List flatKakao = allKakaoPlaceResults.stream() - .filter(Objects::nonNull) - .flatMap(List::stream) - .collect(Collectors.toList()); + logger.info("allKakaoPlaceResults: {}", allKakaoPlaceResults); - if (flatKakao.isEmpty()) { - logger.info("#$# no kakao results. plan={}", plan); - continue; - } - // ---------- 2) Naver 블로그로 title/description 만들기 ---------- - List allBlogsFlat = new ArrayList<>(); - for (KakaoPlaceResDTO k : flatKakao) { - String query = k.getPlaceName() + " " + k.getRoadAddressName(); - List blogs = naverBlogClient.searchNaverBlog(query, naverBlogDisplay); - if (blogs != null) { - allBlogsFlat.addAll(blogs); - } - } - logger.info("allNaverBlogResults.size={}", allBlogsFlat.size()); - - // AI placeList: Kakao 후보 1:1이 가장 안전하지만 현재는 blog기반으로 작성 - // 블로그가 없을 때를 대비해서 Kakao 기본 설명을 fallback으로 변경 - List placeList = new ArrayList<>(); - for (int i = 0; i < flatKakao.size(); i++) { - KakaoPlaceResDTO k = flatKakao.get(i); - // 대응되는 블로그가 없다면 간단한 설명을 생성(fallback) - String title = k.getPlaceName(); - String desc = Optional.ofNullable(k.getCategoryGroupName()).orElse("카테고리 정보 없음") - + " · " + Optional.ofNullable(k.getRoadAddressName()).orElse(k.getAddressName()); - - // 블로그 결과가 있다면 맨 앞 하나만 사용(원한다면 점수화/요약 로직 확장) - if (i < allBlogsFlat.size()) { - NaverBlogResDTO b = allBlogsFlat.get(i); - title = stripHtml(b.getTitle()); - desc = stripHtml(b.getDescription()); - } + // Kakao 평탄화(이 순서를 기준으로 이후 Naver/AI도 동일하게 맞춤) + List flatKakao = allKakaoPlaceResults.stream() + .filter(Objects::nonNull) + .flatMap(List::stream) + .collect(Collectors.toList()); + + if (flatKakao.isEmpty()) { + logger.info("#$# no kakao results. plan={}", plan); + return null; + } - placeList.add(AIReqDTO.builder() - .title(title) - .description(desc) - .build()); + // ---------- 2) Naver 블로그로 title/description 만들기 ---------- + List allBlogsFlat = new ArrayList<>(); + for (KakaoPlaceResDTO k : flatKakao) { + String query = k.getPlaceName() + " " + k.getRoadAddressName(); + List blogs = naverBlogClient.searchNaverBlog(query, naverBlogDisplay); + if (blogs != null) { + allBlogsFlat.addAll(blogs); + } + } + logger.info("allNaverBlogResults.size={}", allBlogsFlat.size()); + + // AI placeList: Kakao 후보 1:1이 가장 안전하지만 현재는 blog기반으로 작성 + // 블로그가 없을 때를 대비해서 Kakao 기본 설명을 fallback으로 변경 + List placeList = new ArrayList<>(); + for (int i = 0; i < flatKakao.size(); i++) { + KakaoPlaceResDTO k = flatKakao.get(i); + // 대응되는 블로그가 없다면 간단한 설명을 생성(fallback) + String title = k.getPlaceName(); + String desc = Optional.ofNullable(k.getCategoryGroupName()).orElse("카테고리 정보 없음") + + " · " + Optional.ofNullable(k.getRoadAddressName()).orElse(k.getAddressName()); + + // 블로그 결과가 있다면 맨 앞 하나만 사용(원한다면 점수화/요약 로직 확장) + if (i < allBlogsFlat.size()) { + NaverBlogResDTO b = allBlogsFlat.get(i); + title = stripHtml(b.getTitle()); + desc = stripHtml(b.getDescription()); } - // ---------- 3) AI 호출 ---------- - // todo. 일정 전체에 대한 태그(schedulePlanTagRegistReqDTOList)도 참고하도록 수정해야 함 - List tagNums = Optional.ofNullable(plan.getPlanTagRegistReqDTOList()) - .orElseGet(List::of) - .stream() - .map(TagRegistReqDTO::getTagNum) - .collect(Collectors.toList()); + placeList.add(AIReqDTO.builder() + .title(title) + .description(desc) + .build()); + } - List tagNames = tagQueryService.findTagNmByTagNum(tagNums); + // ---------- 3) AI 호출 ---------- + // todo. 일정 전체에 대한 태그(schedulePlanTagRegistReqDTOList)도 참고하도록 수정해야 함 + List tagNums = Optional.ofNullable(plan.getPlanTagRegistReqDTOList()) + .orElseGet(List::of) + .stream() + .map(TagRegistReqDTO::getTagNum) + .collect(Collectors.toList()); - AIReqWrapper aiReqWrapper = AIReqWrapper.builder() - .tags(tagNames) - .comment(Optional.ofNullable(scheduleRegistReqDTO.getComment()).orElse("")) - .placeList(placeList) - .build(); + List tagNames = tagQueryService.findTagNmByTagNum(tagNums); - AIResDTO aiRes = aiClient.recommandPlace(aiReqWrapper); + AIReqWrapper aiReqWrapper = AIReqWrapper.builder() + .tags(tagNames) + .comment(Optional.ofNullable(scheduleRegistReqDTO.getComment()).orElse("")) + .placeList(placeList) + .build(); - // ---------- 4) AI가 고른 index → Kakao place 선택 ---------- - Integer idx = (aiRes != null) ? aiRes.getRecommandPlaceIndex() : null; - if (idx == null || idx < 0 || idx >= flatKakao.size()) { - idx = 0; // fallback - } - KakaoPlaceResDTO aiChosen = flatKakao.get(idx); - - // ---------- 5) 응답 DTO 생성 ---------- - String regionNm = null; - if (aiChosen.getRegionNum() != null) { - regionNm = regionRepository.findById(aiChosen.getRegionNum()) - .map(region -> { - // 지역명은 보통 3레벨 > 2레벨 순으로 선택 - if (region.getRegionLevel3() != null && !region.getRegionLevel3().isEmpty()) - return region.getRegionLevel3(); - else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty()) - return region.getRegionLevel2(); - else - return region.getRegionLevel1(); - }) - .orElse(null); - } + AIResDTO aiRes = aiClient.recommandPlace(aiReqWrapper); + + // ---------- 4) AI가 고른 index → Kakao place 선택 ---------- + Integer idx = (aiRes != null) ? aiRes.getRecommandPlaceIndex() : null; + if (idx == null || idx < 0 || idx >= flatKakao.size()) { + idx = 0; // fallback + } + KakaoPlaceResDTO aiChosen = flatKakao.get(idx); + + // ---------- 5) 응답 DTO 생성 ---------- + String regionNm = null; + if (aiChosen.getRegionNum() != null) { + regionNm = regionRepository.findById(aiChosen.getRegionNum()) + .map(region -> { + // 지역명은 보통 3레벨 > 2레벨 순으로 선택 + if (region.getRegionLevel3() != null && !region.getRegionLevel3().isEmpty()) + return region.getRegionLevel3(); + else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty()) + return region.getRegionLevel2(); + else + return region.getRegionLevel1(); + }) + .orElse(null); + } + + return PlanRegistResDTO.builder() + .startTime(plan.getStartTime()) + .endTime(plan.getEndTime()) + .planNm(aiChosen.getPlaceName()) + .planLink(aiChosen.getPlaceUrl()) + .planDescription(aiRes != null ? aiRes.getAiDescription() : null) + .planAddress(Optional.ofNullable(aiChosen.getRoadAddressName()).orElse(aiChosen.getAddressName())) + .regionNm(regionNm) + .regionNum(aiChosen.getRegionNum()) + .categoryNm(categoryNm) + .categoryNum(plan.getCategoryNum()) + .itemNm(itemNm) + .itemNum(plan.getItemNum()) + .planTagRegistResDTOList( + Optional.ofNullable(plan.getPlanTagRegistReqDTOList()) + .orElseGet(List::of) + .stream() + .map(tagReq -> TagRegistResDTO.builder() + .tagNum(tagReq.getTagNum()) + .tagNm(tagReq.getTagNm()) + .build()) + .collect(Collectors.toList()) + ) + // .aiChosen(aiChosen) + .build(); + } - PlanRegistResDTO planRes = PlanRegistResDTO.builder() + + private PlanRegistResDTO handleUserPlan(PlanRegistReqDTO plan) { + + // CASE 1: 카카오 장소 ID로 선택한 경우 + if (plan.getUserAddedPlaceDTO() != null) { + logger.info("#$# ➜ A-1. 사용자가 직접 입력한 플랜 plan={}", plan); + UserAddedPlaceDTO userAddedPlaceDTO = plan.getUserAddedPlaceDTO(); + + RegionDetailVO regionDetailVO = regionQueryService.getRegionNumByAddress(userAddedPlaceDTO.getAddressName()); + String regionNm = resolveRegionName(regionDetailVO); + + logger.info("#$# 사용자가 선택한 장소 userAddedPlaceDTO addressName={}, resolved regionNum={}", userAddedPlaceDTO.getAddressName(), regionDetailVO.getRegionNum()); + + return PlanRegistResDTO.builder() .startTime(plan.getStartTime()) .endTime(plan.getEndTime()) - .planNm(aiChosen.getPlaceName()) - .planLink(aiChosen.getPlaceUrl()) - .planDescription(aiRes != null ? aiRes.getAiDescription() : null) - .planAddress(Optional.ofNullable(aiChosen.getRoadAddressName()).orElse(aiChosen.getAddressName())) - .regionNm(regionNm) - .regionNum(aiChosen.getRegionNum()) - .categoryNm(categoryNm) + .planNm(userAddedPlaceDTO.getPlaceName()) + .planLink(userAddedPlaceDTO.getPlaceUrl()) + .planDescription(null) // 사용자가 추가한 일정은 설명 없음 + .planAddress(userAddedPlaceDTO.getAddressName()) + .regionNum(regionDetailVO.getRegionNum()) //todo. check + .regionNm(regionNm) //todo. check .categoryNum(plan.getCategoryNum()) - .itemNm(itemNm) + .categoryNm(categoryMapper.selectCategoryNmBy(plan.getCategoryNum())) .itemNum(plan.getItemNum()) - .planTagRegistResDTOList( - Optional.ofNullable(plan.getPlanTagRegistReqDTOList()) - .orElseGet(List::of) - .stream() - .map(tagReq -> TagRegistResDTO.builder() - .tagNum(tagReq.getTagNum()) - .tagNm(tagReq.getTagNm()) - .build()) - .collect(Collectors.toList()) - ) - // .aiChosen(aiChosen) + .itemNm(itemMapper.selectItemNmBy(plan.getItemNum())) + .planTagRegistResDTOList(List.of()) // 사용자 일정은 태그 없음 .build(); + } else { - planResList.add(planRes); + // CASE 2: 텍스트로 직접 입력한 경우 + logger.info("#$# ➜ A-2. 사용자가 직접 입력한 플랜 plan={}", plan); - } + logger.info("#$# plan.getRegionRegistReqDTOList().get(0).getRegionNum()={}", plan.getRegionRegistReqDTOList().get(0).getRegionNum()); + RegionDetailVO region = regionQueryService.getRegionByRegionNum(plan.getRegionRegistReqDTOList().get(0).getRegionNum()); + logger.info("#$# regionNm={} {}", region.getRegionLevel2(), region.getRegionLevel3()); - // ---------- 6) DB insert ---------- -// Schedule savedSchedule = registScheduleInfo(scheduleRegistReqDTO, planResList); + return PlanRegistResDTO.builder() + .startTime(plan.getStartTime()) + .endTime(plan.getEndTime()) + .planNm(plan.getPlanNm()) + .planAddress(null) + .planLink(null) + .categoryNum(plan.getCategoryNum()) + .categoryNm(categoryMapper.selectCategoryNmBy(plan.getCategoryNum())) + .itemNum(plan.getItemNum()) + .itemNm(itemMapper.selectItemNmBy(plan.getItemNum())) + .regionNum(plan.getRegionRegistReqDTOList().get(0).getRegionNum()) + .regionNm(region.getRegionLevel3() != null ? region.getRegionLevel3() : region.getRegionLevel2()) + .planTagRegistResDTOList(List.of()) // 사용자 입력은 태그 없음 + .build(); + } - // ---------- 7) ScheduleRegistResDTO 묶어서 리턴 ---------- - return ScheduleRegistResDTO.builder() - .scheduleNm(scheduleNm) - .startDate(startDate) - .endDate(endDate) - .planRegistResDTOList(planResList) - .build(); } - - // 등록완료된 스케쥴의 num을 리턴 public Integer saveSchedule(ScheduleRegistResDTO scheduleRegistResDTO) { return saveScheduleInfo(scheduleRegistResDTO); @@ -598,6 +667,16 @@ private int calLimitPlace(int regionCount) { return perRegionLimit; } + private String resolveRegionName(RegionDetailVO region) { + if (region.getRegionLevel3() != null && !region.getRegionLevel3().isEmpty()) { + return region.getRegionLevel3(); + } else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty()) { + return region.getRegionLevel2(); + } else { + return region.getRegionLevel1(); + } + } + // private String firstRegionName(List regions) { // if (regions == null || regions.isEmpty()) return null; // // RegionRegistReqDTO에 regionName 같은 필드가 있다면 그걸 반환 diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 45494fe..3d64951 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -102,6 +102,11 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 @Operation(summary = "일정 생성 기능", description = "일정을 생성하는 기능입니다.
" + + "- 사용자가 직접 일정을 생성하는 경우는 2가지가 존재합니다.
" + + "     CASE 1. 카카오 장소 검색 API를 사용해서 사용자가 가고 싶은 장소를 선택하는 경우, 카카오 장소 검색 API에서 검색한 placeName, placeUrl, addressName을 보내주세요.
" + + "     CASE 2. 사용자가 세부일정을 직접 텍스트로 입력하는 경우(ex, 친구집 방문), 세부일정명을 planNm 필드에 담아 보내주세요.
" + + "     주의 1) 반드시 isUserAdded=\"Y\"로 전달해 주세요.
" + + "     주의 2) 사용자가 직접 일정을 생성하는 경우 planTagRegistReqDTOList 값을 전달할 필요는 없습니다.
" + "- 생성된 일정은 '일정 등록'과정을 거쳐야 DB에 저장됩니다.
" + "- 사용자가 이 API로 생성된 일정을 확인한 후 '일정 생성하기' 버튼을 누르면 '일정 등록' API를 호출해 주세요.") @PostMapping("/create") @@ -114,24 +119,23 @@ public ApiResponse createSchedule( examples = @ExampleObject( name = "일정 등록 요청 예시", value = "{\n" + - " \"scheduleNm\": \"서울 데이트 코스\",\n" + - " \"startDate\": \"2025-07-01\",\n" + - " \"endDate\": \"2025-07-01\",\n" + + " \"scheduleNm\": \"서울 카페투어\",\n" + + " \"startDate\": \"2025-12-01\",\n" + + " \"endDate\": \"2025-12-01\",\n" + " \"comment\": \"분위기 좋은 카페 추천해주세요\",\n" + " \"scheduleTagRegistReqDTOList\": [\n" + - " { \"tagNm\": \"핫플\", \"tagNum\": 5 },\n" + - " { \"tagNm\": \"활동적인\", \"tagNum\": 8 }\n" + - " ],\n" + + " { \"tagNm\": \"핫플\", \"tagNum\": 5 },\n" + + " { \"tagNm\": \"활동적인\", \"tagNum\": 8 }\n" + + " ],\n" + " \"planRegistReqDTOList\": [\n" + " {\n" + " \"startTime\": \"08:00\",\n" + " \"endTime\": \"09:00\",\n" + " \"itemNum\": 10,\n" + " \"categoryNum\": 2,\n" + + " \"isUserAdded\": \"N\",\n" + " \"regionRegistReqDTOList\": [\n" + - " {\n" + - " \"regionNum\": 1\n" + - " }\n" + + " { \"regionNum\": 1 }\n" + " ],\n" + " \"planTagRegistReqDTOList\": [\n" + " { \"tagNm\": \"디저트맛집\", \"tagNum\": 14 },\n" + @@ -143,34 +147,27 @@ public ApiResponse createSchedule( " \"endTime\": \"15:00\",\n" + " \"itemNum\": 2,\n" + " \"categoryNum\": 1,\n" + - " \"regionRegistReqDTOList\": [\n" + - " {\n" + - " \"regionNum\": 1\n" + - " }\n" + - " ],\n" + - " \"planTagRegistReqDTOList\": [\n" + - " { \"tagNm\": \"조용한\", \"tagNum\": 17 }\n" + - " ]\n" + + " \"isUserAdded\": \"Y\",\n" + + " \"userAddedPlaceDTO\": {\n" + + " \"placeName\": \"카카오프렌즈 코엑스점\",\n" + + " \"placeUrl\": \"http://place.map.kakao.com/26338954\",\n" + + " \"addressName\": \"서울 강남구 삼성동 159\"\n" + + " }\n" + " },\n" + " {\n" + " \"startTime\": \"15:30\",\n" + " \"endTime\": \"19:00\",\n" + " \"itemNum\": 15,\n" + " \"categoryNum\": 4,\n" + + " \"isUserAdded\": \"Y\",\n" + + " \"planNm\": \"친구집 방문\",\n" + " \"regionRegistReqDTOList\": [\n" + - " {\n" + - " \"regionNum\": 1\n" + - " },\n" + - " {\n" + - " \"regionNum\": 2\n" + - " }\n" + - " ],\n" + - " \"planTagRegistReqDTOList\": [\n" + - " { \"tagNm\": \"테마파크\", \"tagNum\": 4 }\n" + + " { \"regionNum\": 1 }\n" + " ]\n" + " }\n" + " ]\n" + "}" + ) ) ) diff --git a/src/main/resources/mapper/RegionMapper.xml b/src/main/resources/mapper/RegionMapper.xml index d136c34..9513361 100644 --- a/src/main/resources/mapper/RegionMapper.xml +++ b/src/main/resources/mapper/RegionMapper.xml @@ -62,6 +62,53 @@ ]]> + + + + + + + From aaf4c1b9ee8b1955b728476382ce281f7d45cbe4 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:04:52 +0900 Subject: [PATCH 29/79] =?UTF-8?q?[FEAT]=20=EB=A9=94=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=20=ED=95=84=EC=9A=94=ED=95=9C=20API=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=20(#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FEAT : 유저 일정 정보 조회 API 작업 * FEAT : 인기 태그 조회 API 작업 * FEAT : 인기 지역 조회 API 작업 * UPDATE 1. 회원 번호 검증 코드 수정 2. 메인 화면 - 다가오는 일정 조회 API 에서 불필요한 데이터 조회하지 않도록 수정 --- .../com/barogagi/config/SecurityConfig.java | 4 +- .../controller/MainPageController.java | 202 ++++++++++++++++++ .../barogagi/mainPage/dto/RegionInfoDTO.java | 13 ++ .../mainPage/dto/RegionRankInfoDTO.java | 14 ++ .../com/barogagi/mainPage/dto/TagInfoDTO.java | 13 ++ .../barogagi/mainPage/dto/TagRankInfoDTO.java | 12 ++ .../mainPage/dto/UserInfoRequestDTO.java | 17 ++ .../mainPage/dto/UserInfoResponseDTO.java | 20 ++ .../mainPage/exception/MainPageException.java | 14 ++ .../mainPage/mapper/MainPageMapper.java | 25 +++ .../mainPage/response/MainPageResponse.java | 24 +++ .../mainPage/service/MainPageService.java | 48 +++++ .../info/controller/InfoController.java | 9 +- .../{ => exception}/MemberInfoException.java | 2 +- src/main/resources/mapper/MainPageMapper.xml | 124 +++++++++++ 15 files changed, 535 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/barogagi/mainPage/controller/MainPageController.java create mode 100644 src/main/java/com/barogagi/mainPage/dto/RegionInfoDTO.java create mode 100644 src/main/java/com/barogagi/mainPage/dto/RegionRankInfoDTO.java create mode 100644 src/main/java/com/barogagi/mainPage/dto/TagInfoDTO.java create mode 100644 src/main/java/com/barogagi/mainPage/dto/TagRankInfoDTO.java create mode 100644 src/main/java/com/barogagi/mainPage/dto/UserInfoRequestDTO.java create mode 100644 src/main/java/com/barogagi/mainPage/dto/UserInfoResponseDTO.java create mode 100644 src/main/java/com/barogagi/mainPage/exception/MainPageException.java create mode 100644 src/main/java/com/barogagi/mainPage/mapper/MainPageMapper.java create mode 100644 src/main/java/com/barogagi/mainPage/response/MainPageResponse.java create mode 100644 src/main/java/com/barogagi/mainPage/service/MainPageService.java rename src/main/java/com/barogagi/member/info/{ => exception}/MemberInfoException.java (85%) create mode 100644 src/main/resources/mapper/MainPageMapper.xml diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index 1fb8774..ab8980b 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -36,7 +36,9 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, "/auth/**", "/login/**", "/membership/join/**", - "/terms/**" + "/terms/**", + "/main/page/popular/tag/list", + "main/page/popular/region/list" }; @Bean diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java new file mode 100644 index 0000000..966299d --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -0,0 +1,202 @@ +package com.barogagi.mainPage.controller; + +import com.barogagi.config.vo.DefaultVO; +import com.barogagi.mainPage.dto.*; +import com.barogagi.mainPage.exception.MainPageException; +import com.barogagi.mainPage.response.MainPageResponse; +import com.barogagi.mainPage.service.MainPageService; +import com.barogagi.response.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +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.RestController; + +import java.util.List; + +@Tag(name = "메인 화면", description = "메인 화면에 필요한 API") +@RestController +@RequestMapping("/main/page") +public class MainPageController { + + private static final Logger logger = LoggerFactory.getLogger(MainPageController.class); + + private final MainPageService mainPageService; + + private final String API_SECRET_KEY; + + @Autowired + public MainPageController(MainPageService mainPageService, + Environment environment) { + this.mainPageService = mainPageService; + this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + } + + @Operation(summary = "유저 일정 정보 API", description = "메인 화면 - 다가오는 일정 부분에 해당하는 API") + @PostMapping("/user/schedule/info") + public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { + + logger.info("CALL /main/page/user/schedule/info"); + + MainPageResponse mainPageResponse = new MainPageResponse(); + String resultCode = ""; + String message = ""; + + try { + + // 회원번호 + Object membershipNoAttr = request.getAttribute("membershipNo"); + if (membershipNoAttr == null) { + throw new MainPageException("401", "접근 권한이 존재하지 않습니다."); + } + String membershipNo = String.valueOf(membershipNoAttr); + + // 유저 일정 정보 API + UserInfoRequestDTO userInfoRequestDTO = new UserInfoRequestDTO(); + userInfoRequestDTO.setMembershipNo(membershipNo); + + UserInfoResponseDTO userInfoResponseDTO = mainPageService.selectUserScheduleInfo(userInfoRequestDTO); + + if(null == userInfoResponseDTO) { + resultCode = "201"; + message = "일정이 존재하지 않습니다."; + + } else { + + resultCode = "200"; + message = "조회 성공하였습니다."; + + mainPageResponse.setUserInfoResponseDTO(userInfoResponseDTO); + + // 일정 번호 + userInfoRequestDTO.setScheduleNum(userInfoResponseDTO.getScheduleNum()); + + // 해당 schedule에 대한 태그 목록 조회 + List tagList = mainPageService.selectScheduleTag(userInfoRequestDTO); + + if(!tagList.isEmpty()) { + mainPageResponse.setTagInfoList(tagList); + } + + // 해당 plan에 대한 region 정보 조회 + userInfoRequestDTO.setPlanNum(userInfoResponseDTO.getPlanNum()); + + RegionInfoDTO regionInfo = mainPageService.selectScheduleRegionInfo(userInfoRequestDTO); + + if(null != regionInfo) { + mainPageResponse.setRegionInfoDTO(regionInfo); + } + } + + } catch (MainPageException ex) { + resultCode = ex.getResultCode(); + message = ex.getMessage(); + + } catch (Exception e) { + logger.error("error", e); + resultCode = "400"; + message = "오류가 발생하였습니다."; + } finally { + mainPageResponse.setResultCode(resultCode); + mainPageResponse.setMessage(message); + } + + return mainPageResponse; + } + + @Operation(summary = "인기 태그 조회 API ", description = "메인 화면 - 오늘 많이 생성되는 일정 부분에 해당하는 API") + @PostMapping("/popular/tag/list") + public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { + + logger.info("CALL /main/page/popular/tag/list"); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + if(!vo.getApiSecretKey().equals(API_SECRET_KEY)) { + throw new MainPageException("100", "잘못된 접근입니다."); + } + + // 인기 태그 조회 + List tagRankInfoList = mainPageService.selectTagRankList(); + + logger.info("@@ !tagRankInfoList.isEmpty()={}", !tagRankInfoList.isEmpty()); + if(!tagRankInfoList.isEmpty()) { + resultCode = "200"; + message = "인기 태그 조회 완료하였습니다."; + apiResponse.setData(tagRankInfoList); + } else { + resultCode = "201"; + message = "인기 태그 목록이 존재하지 않습니다."; + } + + } catch (MainPageException ex) { + resultCode = ex.getResultCode(); + message = ex.getMessage(); + + } catch (Exception e) { + logger.error("error", e); + resultCode = "400"; + message = "오류가 발생하였습니다."; + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); + } + + return apiResponse; + } + + @Operation(summary = "인기 지역 조회 API ", description = "메인 화면 - 지금 인기많은 핫 플레이스 부분에 해당하는 API") + @PostMapping("/popular/region/list") + public ApiResponse selectPopularRegionList(@RequestBody DefaultVO vo) { + + logger.info("CALL /main/page/popular/region/list"); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + if(!vo.getApiSecretKey().equals(API_SECRET_KEY)) { + throw new MainPageException("100", "잘못된 접근입니다."); + } + + // 인기 지역 조회 + List regionRankInfoList = mainPageService.selectRegionRankList(); + + logger.info("@@ !regionRankInfoList.isEmpty()={}", !regionRankInfoList.isEmpty()); + if(!regionRankInfoList.isEmpty()) { + resultCode = "200"; + message = "인기 지역 조회 완료하였습니다."; + apiResponse.setData(regionRankInfoList); + } else { + resultCode = "201"; + message = "인기 지역 목록이 존재하지 않습니다."; + } + + } catch (MainPageException ex) { + resultCode = ex.getResultCode(); + message = ex.getMessage(); + + } catch (Exception e) { + logger.error("error", e); + resultCode = "400"; + message = "오류가 발생하였습니다."; + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); + } + + return apiResponse; + } +} diff --git a/src/main/java/com/barogagi/mainPage/dto/RegionInfoDTO.java b/src/main/java/com/barogagi/mainPage/dto/RegionInfoDTO.java new file mode 100644 index 0000000..1fd195c --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/dto/RegionInfoDTO.java @@ -0,0 +1,13 @@ +package com.barogagi.mainPage.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RegionInfoDTO { + private String regionLevel1 = ""; + private String regionLevel2 = ""; + private String regionLevel3 = ""; + private String regionLevel4 = ""; +} diff --git a/src/main/java/com/barogagi/mainPage/dto/RegionRankInfoDTO.java b/src/main/java/com/barogagi/mainPage/dto/RegionRankInfoDTO.java new file mode 100644 index 0000000..998479f --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/dto/RegionRankInfoDTO.java @@ -0,0 +1,14 @@ +package com.barogagi.mainPage.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RegionRankInfoDTO { + private String regionLevel1 = ""; + private String regionLevel2 = ""; + private String regionLevel3 = ""; + private String regionLevel4 = ""; + private int rankNo = 0; +} diff --git a/src/main/java/com/barogagi/mainPage/dto/TagInfoDTO.java b/src/main/java/com/barogagi/mainPage/dto/TagInfoDTO.java new file mode 100644 index 0000000..3f220f0 --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/dto/TagInfoDTO.java @@ -0,0 +1,13 @@ +package com.barogagi.mainPage.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TagInfoDTO { + + private int tagNum = 0; + private String tagNm; + private String tagType; +} diff --git a/src/main/java/com/barogagi/mainPage/dto/TagRankInfoDTO.java b/src/main/java/com/barogagi/mainPage/dto/TagRankInfoDTO.java new file mode 100644 index 0000000..8faf910 --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/dto/TagRankInfoDTO.java @@ -0,0 +1,12 @@ +package com.barogagi.mainPage.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TagRankInfoDTO { + + private String tagNm = ""; + private int rankNo = 0; +} diff --git a/src/main/java/com/barogagi/mainPage/dto/UserInfoRequestDTO.java b/src/main/java/com/barogagi/mainPage/dto/UserInfoRequestDTO.java new file mode 100644 index 0000000..a50c69b --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/dto/UserInfoRequestDTO.java @@ -0,0 +1,17 @@ +package com.barogagi.mainPage.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UserInfoRequestDTO { + + private String membershipNo = ""; + + // SCHEDULE + private int scheduleNum = 0; + + // PLAN + private int planNum = 0; +} diff --git a/src/main/java/com/barogagi/mainPage/dto/UserInfoResponseDTO.java b/src/main/java/com/barogagi/mainPage/dto/UserInfoResponseDTO.java new file mode 100644 index 0000000..880df60 --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/dto/UserInfoResponseDTO.java @@ -0,0 +1,20 @@ +package com.barogagi.mainPage.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class UserInfoResponseDTO { + + // SCHEDULE + private int scheduleNum = 0; + private String scheduleNm = ""; + private String startDate = ""; + + // PLAN + private int planNum = 0; + + // CATEGORY + private String categoryNm = ""; +} diff --git a/src/main/java/com/barogagi/mainPage/exception/MainPageException.java b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java new file mode 100644 index 0000000..d42c740 --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java @@ -0,0 +1,14 @@ +package com.barogagi.mainPage.exception; + +import lombok.Getter; + +@Getter +public class MainPageException extends RuntimeException { + + private final String resultCode; + + public MainPageException(String resultCode, String message) { + super(message); + this.resultCode = resultCode; + } +} diff --git a/src/main/java/com/barogagi/mainPage/mapper/MainPageMapper.java b/src/main/java/com/barogagi/mainPage/mapper/MainPageMapper.java new file mode 100644 index 0000000..e2163e0 --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/mapper/MainPageMapper.java @@ -0,0 +1,25 @@ +package com.barogagi.mainPage.mapper; + +import com.barogagi.mainPage.dto.*; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface MainPageMapper { + + // 유저 일정 조회 + UserInfoResponseDTO selectUserScheduleInfo(UserInfoRequestDTO userInfoRequestDTO); + + // 해당 schedule에 대한 태그 목록 조회 + List selectScheduleTag(UserInfoRequestDTO userInfoRequestDTO); + + // 해당 plan에 대한 region 정보 조회 + RegionInfoDTO selectScheduleRegionInfo(UserInfoRequestDTO userInfoRequestDTO); + + // 인기 지역 조회 + List selectRegionRankList(); + + // 인기 태그 조회 + List selectTagRankList(); +} diff --git a/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java b/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java new file mode 100644 index 0000000..22b484f --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java @@ -0,0 +1,24 @@ +package com.barogagi.mainPage.response; + +import com.barogagi.mainPage.dto.RegionInfoDTO; +import com.barogagi.mainPage.dto.TagInfoDTO; +import com.barogagi.mainPage.dto.UserInfoResponseDTO; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class MainPageResponse { + // 결과 코드 + private String resultCode; + + // 결과 메시지 + private String message; + + // 데이터 + private UserInfoResponseDTO userInfoResponseDTO; + private List tagInfoList; + private RegionInfoDTO regionInfoDTO; +} diff --git a/src/main/java/com/barogagi/mainPage/service/MainPageService.java b/src/main/java/com/barogagi/mainPage/service/MainPageService.java new file mode 100644 index 0000000..ff6e9f4 --- /dev/null +++ b/src/main/java/com/barogagi/mainPage/service/MainPageService.java @@ -0,0 +1,48 @@ +package com.barogagi.mainPage.service; + +import com.barogagi.mainPage.dto.*; +import com.barogagi.mainPage.mapper.MainPageMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class MainPageService { + + private static final Logger logger = LoggerFactory.getLogger(MainPageService.class); + + private final MainPageMapper mainPageMapper; + + @Autowired + public MainPageService(MainPageMapper mainPageMapper) { + this.mainPageMapper = mainPageMapper; + } + + // 유저 일정 조회 + public UserInfoResponseDTO selectUserScheduleInfo(UserInfoRequestDTO userInfoRequestDTO) throws Exception { + return mainPageMapper.selectUserScheduleInfo(userInfoRequestDTO); + } + + // 해당 schedule에 대한 태그 목록 조회 + public List selectScheduleTag(UserInfoRequestDTO userInfoRequestDTO) throws Exception { + return mainPageMapper.selectScheduleTag(userInfoRequestDTO); + } + + // 해당 plan에 대한 region 정보 조회 + public RegionInfoDTO selectScheduleRegionInfo(UserInfoRequestDTO userInfoRequestDTO) throws Exception { + return mainPageMapper.selectScheduleRegionInfo(userInfoRequestDTO); + } + + // 인기 지역 조회 + public List selectRegionRankList() throws Exception { + return mainPageMapper.selectRegionRankList(); + } + + // 인기 태그 조회 + public List selectTagRankList() throws Exception { + return mainPageMapper.selectTagRankList(); + } +} diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index 4f6a822..e1b0ea5 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -3,7 +3,7 @@ import com.barogagi.config.PasswordConfig; import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.service.JoinService; -import com.barogagi.member.info.MemberInfoException; +import com.barogagi.member.info.exception.MemberInfoException; import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.dto.MemberRequestDTO; import com.barogagi.member.info.service.MemberService; @@ -55,12 +55,13 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { try { - String membershipNo = String.valueOf(request.getAttribute("membershipNo")); - logger.info("@@ membershipNo.isEmpty()={}", membershipNo.isEmpty()); - if (membershipNo.isEmpty()) { + Object membershipNoAttr = request.getAttribute("membershipNo"); + if(membershipNoAttr == null) { throw new MemberInfoException("401", "접근 권한이 존재하지 않습니다."); } + String membershipNo = String.valueOf(membershipNoAttr); + // 회원 정보 조회 Member memberInfo = memberService.findByMembershipNo(membershipNo); if(null == memberInfo) { diff --git a/src/main/java/com/barogagi/member/info/MemberInfoException.java b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java similarity index 85% rename from src/main/java/com/barogagi/member/info/MemberInfoException.java rename to src/main/java/com/barogagi/member/info/exception/MemberInfoException.java index cc9b2ba..3e7c3f7 100644 --- a/src/main/java/com/barogagi/member/info/MemberInfoException.java +++ b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java @@ -1,4 +1,4 @@ -package com.barogagi.member.info; +package com.barogagi.member.info.exception; import lombok.Getter; diff --git a/src/main/resources/mapper/MainPageMapper.xml b/src/main/resources/mapper/MainPageMapper.xml new file mode 100644 index 0000000..0ae09a0 --- /dev/null +++ b/src/main/resources/mapper/MainPageMapper.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + From 1b48b9d4884e060cce40ac2b5a49ea29e50e4d39 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Tue, 25 Nov 2025 19:59:08 +0900 Subject: [PATCH 30/79] =?UTF-8?q?feat:=20=EB=B0=B1=EC=97=94=EB=93=9C=20dep?= =?UTF-8?q?loy=20=EC=84=A4=EC=A0=95=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-backend.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index efe64c5..6c0ee52 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -28,7 +28,7 @@ jobs: else echo "HOST=${{ secrets.TEST_HOST }}" >> $GITHUB_ENV echo "TARGET_DIR=${{ secrets.TEST_TARGET_DIR }}" >> $GITHUB_ENV - echo "${{ secrets.APP_PROPS_MAIN }}" > ./src/main/resources/application.properties + echo "${{ secrets.APP_PROPS_MAIN_BASE64 }}" | base64 --decode > ./src/main/resources/application.properties fi - name: Build jar file From 1855da0bca011a06f7d7c03a2355b9dddb552934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EB=8B=A4=EB=AF=BC?= Date: Tue, 25 Nov 2025 20:53:23 +0900 Subject: [PATCH 31/79] =?UTF-8?q?[FEAT]=20=EC=9D=BC=EC=A0=95=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20API=20=EC=9E=91=EC=97=85=20(#19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 카카오 일정 조회 구현 중 * fix: import 임시 주석 처리 * feat: 일정 등록시 카카오 API 조회 * feat: 카카오 장소 조회 기능 추가 * feat: 네이버 블로그 조회 기능 추가 * feat: 태그번호로 태그명 조회 기능 * feat: ai 추천 로직 진행중 * feat: ai 일정 추천 기능 진행중 * feat: ai 일정 추천 결과 리턴하도록 작업 * chore: 필요 없는 로그 주석처리 * fix: tag 조회 쿼리 테이블 명 대문자로 수정(소문자 사용 시 에러) * chore: AIClient 필요없는 주석 제거 * feat: 주소를 x,y 좌표로 변환하는 api 추가 * feat: ApiResponse에 success/error 정적팩토리 메서드 추가 * feat: 주소 목록 검색 api 추가 * fix: naverBlog 패키지 이름 수정 * fix: tagList를 int 배열이 아니라 map 형태로 보내주도록 수정 * fix: 일정 생성 기능 수정 * feat: 생성된 일정을 DB에 저장하는 API 작업 * update: 일정 상세 조회 API의 태그 조회 쿼리 수정 * feat: 내 일정 목록 조회 API 작업 * update: Plan, Schedule 엔티티에 등록일, 수정일, 삭제여부 컬럼 추가 * update: Plan, Schedule 저장시 삭제여부 함께 저장, 일정 저장 API 메서드명 수정 * feat: 전체 일정 삭제 API 작업 * feat: 일정 수정, 부분삭제 API 작업 * feat: 카카오 장소 키워드 검색 API 추가 * fix: 일정 수정 시 soft-delete 하도록 변경 * feat: 사용자가 직접 추가한 일정, 카카오 API로 검색한 일정 생성할 수 있도록 수정 --- .../java/com/barogagi/ai/client/AIClient.java | 113 +++ .../java/com/barogagi/ai/dto/AIReqDTO.java | 13 + .../com/barogagi/ai/dto/AIReqWrapper.java | 15 + .../java/com/barogagi/ai/dto/AIResDTO.java | 13 + .../java/com/barogagi/ai/dto/ChatMessage.java | 13 + .../java/com/barogagi/ai/dto/ChatRequest.java | 16 + .../kakaoplace/client/KakaoGeoCodeClient.java | 49 ++ .../kakaoplace/client/KakaoPlaceClient.java | 76 ++ .../kakaoplace/dto/KakaoGeoCodeResDTO.java | 19 + .../dto/KakaoGeoCodeSearchResDTO.java | 18 + .../kakaoplace/dto/KakaoPlaceResDTO.java | 46 ++ .../dto/KakaoPlaceSearchResDTO.java | 19 + .../naverblog/client/NaverBlogClient.java | 58 ++ .../naverblog/dto/NaverBlogResDTO.java | 33 + .../naverblog/dto/NaverBlogSearchResDTO.java | 30 + .../barogagi/plan/command/entity/Plan.java | 44 ++ .../command/repository/ItemRepository.java | 9 + .../command/repository/PlanRepository.java | 5 + .../command/repository/PlanTagRepository.java | 7 + .../plan/controller/PlaceController.java | 57 ++ .../barogagi/plan/dto/PlanDetailResDTO.java | 20 - .../barogagi/plan/dto/PlanRegistReqDTO.java | 26 +- .../barogagi/plan/dto/PlanRegistResDTO.java | 62 ++ .../barogagi/plan/dto/UserAddedPlaceDTO.java | 14 + .../plan/query/mapper/CategoryMapper.java | 13 + .../plan/query/mapper/ItemMapper.java | 10 + .../plan/query/service/PlaceQueryService.java | 25 + .../plan/query/service/PlanQueryService.java | 5 +- .../barogagi/region/command/entity/Place.java | 40 ++ .../region/command/entity/PlanRegion.java | 17 +- .../region/command/entity/PlanRegionId.java | 19 + .../region/command/entity/PlanRegionNum.java | 22 - .../command/repository/PlaceRepository.java | 7 + .../repository/PlanRegionRepository.java | 7 + .../region/controller/RegionController.java | 106 +++ .../region/dto/RegionGeoCodeResDTO.java | 27 + .../region/dto/RegionRegistReqDTO.java | 26 +- .../region/dto/RegionSearchResDTO.java | 14 + .../region/query/mapper/RegionMapper.java | 17 + .../query/service/RegionGeoCodeService.java | 79 ++ .../query/service/RegionQueryService.java | 144 ++++ .../com/barogagi/response/ApiResponse.java | 15 + .../schedule/command/entity/Schedule.java | 35 +- .../repository/ScheduleRepository.java | 3 + .../service/ScheduleCommandService.java | 679 ++++++++++++++++++ .../controller/ScheduleController.java | 399 ++++++++-- .../schedule/dto/KakaoPlaceReqDTO.java | 11 + .../schedule/dto/ScheduleListResDTO.java | 22 + .../schedule/dto/ScheduleRegistReqDTO.java | 8 +- .../schedule/dto/ScheduleRegistResDTO.java | 30 + .../schedule/query/mapper/ScheduleMapper.java | 6 + .../query/service/ScheduleQueryService.java | 19 + .../schedule/query/vo/ScheduleListVO.java | 18 + .../barogagi/tag/command/entity/PlanTag.java | 28 + .../tag/command/entity/PlanTagId.java | 19 + .../tag/command/entity/ScheduleTag.java | 29 + .../tag/command/entity/ScheduleTagId.java | 19 + .../com/barogagi/tag/command/entity/Tag.java | 33 + .../repository/ScheduleTagRepository.java | 14 + .../tag/command/repository/TagRepository.java | 12 + .../tag/controller/TagController.java | 54 ++ .../com/barogagi/tag/dto/TagRegistResDTO.java | 18 + .../com/barogagi/tag/dto/TagSearchReqDTO.java | 14 + .../com/barogagi/tag/dto/TagSearchResDTO.java | 16 + .../java/com/barogagi/tag/enums/TagType.java | 31 + .../barogagi/tag/query/mapper/TagMapper.java | 8 + .../tag/query/service/TagQueryService.java | 44 ++ .../barogagi/tag/query/vo/TagDetailVO.java | 3 + .../barogagi/tag/query/vo/TagSimpleVO.java | 11 + .../java/com/barogagi/util/HtmlUtils.java | 37 + src/main/resources/mapper/CategoryMapper.xml | 19 + src/main/resources/mapper/ItemMapper.xml | 9 +- src/main/resources/mapper/PlanMapper.xml | 3 +- src/main/resources/mapper/RegionMapper.xml | 90 +++ src/main/resources/mapper/ScheduleMapper.xml | 29 + src/main/resources/mapper/TagMapper.xml | 38 + 76 files changed, 3056 insertions(+), 120 deletions(-) create mode 100644 src/main/java/com/barogagi/ai/client/AIClient.java create mode 100644 src/main/java/com/barogagi/ai/dto/AIReqDTO.java create mode 100644 src/main/java/com/barogagi/ai/dto/AIReqWrapper.java create mode 100644 src/main/java/com/barogagi/ai/dto/AIResDTO.java create mode 100644 src/main/java/com/barogagi/ai/dto/ChatMessage.java create mode 100644 src/main/java/com/barogagi/ai/dto/ChatRequest.java create mode 100644 src/main/java/com/barogagi/kakaoplace/client/KakaoGeoCodeClient.java create mode 100644 src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java create mode 100644 src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeResDTO.java create mode 100644 src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeSearchResDTO.java create mode 100644 src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java create mode 100644 src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java create mode 100644 src/main/java/com/barogagi/naverblog/client/NaverBlogClient.java create mode 100644 src/main/java/com/barogagi/naverblog/dto/NaverBlogResDTO.java create mode 100644 src/main/java/com/barogagi/naverblog/dto/NaverBlogSearchResDTO.java create mode 100644 src/main/java/com/barogagi/plan/command/repository/ItemRepository.java create mode 100644 src/main/java/com/barogagi/plan/command/repository/PlanTagRepository.java create mode 100644 src/main/java/com/barogagi/plan/controller/PlaceController.java delete mode 100644 src/main/java/com/barogagi/plan/dto/PlanDetailResDTO.java create mode 100644 src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java create mode 100644 src/main/java/com/barogagi/plan/dto/UserAddedPlaceDTO.java create mode 100644 src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java create mode 100644 src/main/java/com/barogagi/plan/query/mapper/ItemMapper.java create mode 100644 src/main/java/com/barogagi/plan/query/service/PlaceQueryService.java create mode 100644 src/main/java/com/barogagi/region/command/entity/Place.java create mode 100644 src/main/java/com/barogagi/region/command/entity/PlanRegionId.java delete mode 100644 src/main/java/com/barogagi/region/command/entity/PlanRegionNum.java create mode 100644 src/main/java/com/barogagi/region/command/repository/PlaceRepository.java create mode 100644 src/main/java/com/barogagi/region/command/repository/PlanRegionRepository.java create mode 100644 src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java create mode 100644 src/main/java/com/barogagi/region/dto/RegionSearchResDTO.java create mode 100644 src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java create mode 100644 src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java create mode 100644 src/main/java/com/barogagi/schedule/dto/ScheduleListResDTO.java create mode 100644 src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java create mode 100644 src/main/java/com/barogagi/schedule/query/vo/ScheduleListVO.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/PlanTag.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/PlanTagId.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/ScheduleTagId.java create mode 100644 src/main/java/com/barogagi/tag/command/entity/Tag.java create mode 100644 src/main/java/com/barogagi/tag/command/repository/ScheduleTagRepository.java create mode 100644 src/main/java/com/barogagi/tag/command/repository/TagRepository.java create mode 100644 src/main/java/com/barogagi/tag/controller/TagController.java create mode 100644 src/main/java/com/barogagi/tag/dto/TagRegistResDTO.java create mode 100644 src/main/java/com/barogagi/tag/dto/TagSearchReqDTO.java create mode 100644 src/main/java/com/barogagi/tag/dto/TagSearchResDTO.java create mode 100644 src/main/java/com/barogagi/tag/enums/TagType.java create mode 100644 src/main/java/com/barogagi/tag/query/service/TagQueryService.java create mode 100644 src/main/java/com/barogagi/tag/query/vo/TagSimpleVO.java create mode 100644 src/main/java/com/barogagi/util/HtmlUtils.java create mode 100644 src/main/resources/mapper/CategoryMapper.xml diff --git a/src/main/java/com/barogagi/ai/client/AIClient.java b/src/main/java/com/barogagi/ai/client/AIClient.java new file mode 100644 index 0000000..0ab4b0b --- /dev/null +++ b/src/main/java/com/barogagi/ai/client/AIClient.java @@ -0,0 +1,113 @@ +package com.barogagi.ai.client; + +import com.barogagi.ai.dto.AIReqWrapper; +import com.barogagi.ai.dto.AIResDTO; +import com.barogagi.ai.dto.ChatMessage; +import com.barogagi.ai.dto.ChatRequest; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.util.List; +import java.util.stream.Collectors; + +import static com.barogagi.util.HtmlUtils.stripCodeFence; + +@Service +public class AIClient { + private static final Logger logger = LoggerFactory.getLogger(AIClient.class); + + @Value("${ai.api.base-url}") + private String aiBaseUrl; + + @Value("${ai.api.path}") + private String aiPath; + + @Value("${ai.api.key}") + private String aiApiKey; + + @Value("${ai.prompt.system}") + private String aiPrompt; + + @Value("${ai.model}") + private String aiModel; + + private final RestTemplate restTemplate = new RestTemplate(); + private static final ObjectMapper OM = new ObjectMapper(); + + public AIResDTO recommandPlace(AIReqWrapper aiReqWrapper) { + String url = aiBaseUrl + aiPath; + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(aiApiKey); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setAccept(List.of(MediaType.APPLICATION_JSON)); + + ChatRequest chatRequest = buildChatRequest(aiReqWrapper); + HttpEntity entity = new HttpEntity<>(chatRequest, headers); + + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.POST, + entity, + String.class + ); + + String body = response.getBody(); + + ObjectMapper om = new ObjectMapper(); + try { + // 1) content 추출 + JsonNode root = om.readTree(body); + JsonNode contentNode = root.path("choices").get(0).path("message").path("content"); + String content = contentNode.asText(); + + // 2) 코드펜스/여분 공백 제거 + content = stripCodeFence(content); + + // 3) content(JSON 문자열) -> DTO + AIResDTO dto = om.readValue(content, AIResDTO.class); + return dto; + + } catch (com.fasterxml.jackson.core.JsonProcessingException e) { + // JsonMappingException 포함해서 모두 여기서 처리됨 + return null; // 혹은 throw new IllegalStateException("AI 응답 파싱 실패", e); + } + } + + // ai에게 요청 가능한 형태로 변경 + private ChatRequest buildChatRequest(AIReqWrapper wrapper) { + // system 메시지 + ChatMessage systemMsg = ChatMessage.builder() + .role("system") + .content(aiPrompt) + .build(); + + // user 메시지: AIReqWrapper → 문자열 변환 + StringBuilder sb = new StringBuilder(); + sb.append("comment: ").append(wrapper.getComment()).append("\n"); + sb.append("tags: ").append(String.join(", ", wrapper.getTags())).append("\n\n"); + + sb.append("places:\n"); + String placesText = wrapper.getPlaceList().stream() + .map(p -> "- title: " + p.getTitle() + "\n description: " + p.getDescription()) + .collect(Collectors.joining("\n")); + sb.append(placesText); + + ChatMessage userMsg = ChatMessage.builder() + .role("user") + .content(sb.toString()) + .build(); + + return ChatRequest.builder() + .model(aiModel) + .messages(List.of(systemMsg, userMsg)) + .max_tokens(500) + .build(); + } +} diff --git a/src/main/java/com/barogagi/ai/dto/AIReqDTO.java b/src/main/java/com/barogagi/ai/dto/AIReqDTO.java new file mode 100644 index 0000000..95f8fbc --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/AIReqDTO.java @@ -0,0 +1,13 @@ +package com.barogagi.ai.dto; + +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class AIReqDTO { + private String title; + private String description; +} diff --git a/src/main/java/com/barogagi/ai/dto/AIReqWrapper.java b/src/main/java/com/barogagi/ai/dto/AIReqWrapper.java new file mode 100644 index 0000000..53fc097 --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/AIReqWrapper.java @@ -0,0 +1,15 @@ +package com.barogagi.ai.dto; +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class AIReqWrapper { + private List tags; + private String comment; + private List placeList; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/ai/dto/AIResDTO.java b/src/main/java/com/barogagi/ai/dto/AIResDTO.java new file mode 100644 index 0000000..506fc57 --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/AIResDTO.java @@ -0,0 +1,13 @@ +package com.barogagi.ai.dto; + +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class AIResDTO { + private Integer recommandPlaceIndex; + private String aiDescription; +} diff --git a/src/main/java/com/barogagi/ai/dto/ChatMessage.java b/src/main/java/com/barogagi/ai/dto/ChatMessage.java new file mode 100644 index 0000000..2042ec6 --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/ChatMessage.java @@ -0,0 +1,13 @@ +package com.barogagi.ai.dto; + +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class ChatMessage { + private String role; // "system" | "user" | "assistant" + private String content; // 메시지 내용 +} diff --git a/src/main/java/com/barogagi/ai/dto/ChatRequest.java b/src/main/java/com/barogagi/ai/dto/ChatRequest.java new file mode 100644 index 0000000..61f9355 --- /dev/null +++ b/src/main/java/com/barogagi/ai/dto/ChatRequest.java @@ -0,0 +1,16 @@ +package com.barogagi.ai.dto; + +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +public class ChatRequest { + private String model; + private List messages; + private int max_tokens; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/kakaoplace/client/KakaoGeoCodeClient.java b/src/main/java/com/barogagi/kakaoplace/client/KakaoGeoCodeClient.java new file mode 100644 index 0000000..611e604 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/client/KakaoGeoCodeClient.java @@ -0,0 +1,49 @@ +package com.barogagi.kakaoplace.client; + +import com.barogagi.kakaoplace.dto.KakaoGeoCodeResDTO; +import com.barogagi.kakaoplace.dto.KakaoGeoCodeSearchResDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; + +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.List; + +@Service +public class KakaoGeoCodeClient { + private static final Logger logger = LoggerFactory.getLogger(KakaoGeoCodeClient.class); + + @Value("${kakao.rest-api-key}") + private String kakaoApiKey; + + private final RestTemplate restTemplate = new RestTemplate(); + + public List convertKakaoGeoCode(String address) { + String url = String.valueOf(UriComponentsBuilder +// .fromHttpUrl("https://dapi.kakao.com/v2/local/search/keyword.json") + .fromHttpUrl("https://dapi.kakao.com/v2/local/search/address.json") + .queryParam("query", address) + .build(false)); + + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "KakaoAK " + kakaoApiKey); + + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + KakaoGeoCodeSearchResDTO.class + ); + + List body = response.getBody().getDocuments(); + return body; + } +} diff --git a/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java new file mode 100644 index 0000000..559c38c --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/client/KakaoPlaceClient.java @@ -0,0 +1,76 @@ +package com.barogagi.kakaoplace.client; + +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import com.barogagi.kakaoplace.dto.KakaoPlaceSearchResDTO; +import com.barogagi.schedule.command.service.ScheduleCommandService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.List; + +@Service +public class KakaoPlaceClient { + private static final Logger logger = LoggerFactory.getLogger(KakaoPlaceClient.class); + + @Value("${kakao.rest-api-key}") + private String kakaoApiKey; + + private final RestTemplate restTemplate = new RestTemplate(); + + public List searchKakaoPlace(String query, String x, String y, int radius, int limitPlace) { + String url = String.valueOf(UriComponentsBuilder + .fromHttpUrl("https://dapi.kakao.com/v2/local/search/keyword.json") + .queryParam("query", query) + .queryParam("x", x) + .queryParam("y", y) + .queryParam("radius", radius) + .queryParam("size", limitPlace) + .build(false)); + + + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "KakaoAK " + kakaoApiKey); + + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + KakaoPlaceSearchResDTO.class + ); + + List body = response.getBody().getDocuments(); + return body; + } + + public List searchKakaoPlaceByKeyword(String query) { + String url = String.valueOf(UriComponentsBuilder + .fromHttpUrl("https://dapi.kakao.com/v2/local/search/keyword.json") + .queryParam("query", query) + .build(false)); + + + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "KakaoAK " + kakaoApiKey); + + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + KakaoPlaceSearchResDTO.class + ); + + List body = response.getBody().getDocuments(); + return body; + } + +} diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeResDTO.java new file mode 100644 index 0000000..d05a996 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeResDTO.java @@ -0,0 +1,19 @@ +package com.barogagi.kakaoplace.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "카카오 주소로 좌표 변환 DTO") +public class KakaoGeoCodeResDTO { + @JsonProperty("x") + private String x; + + @JsonProperty("y") + private String y; +} diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeSearchResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeSearchResDTO.java new file mode 100644 index 0000000..33b7245 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoGeoCodeSearchResDTO.java @@ -0,0 +1,18 @@ +package com.barogagi.kakaoplace.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "카카오 주소로 좌표 변환 결과 DTO") +public class KakaoGeoCodeSearchResDTO { + @JsonProperty("documents") + private List documents; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java new file mode 100644 index 0000000..815c7d0 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceResDTO.java @@ -0,0 +1,46 @@ +package com.barogagi.kakaoplace.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Getter @Setter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "카카오 장소 추천 목록 DTO") +public class KakaoPlaceResDTO { + + @JsonProperty("address_name") + private String addressName; + + @JsonProperty("place_name") + private String placeName; + + @JsonProperty("category_group_name") + private String categoryGroupName; + + @JsonProperty("distance") + private String distance; + + @JsonProperty("id") + private String id; + + @JsonProperty("x") + private String x; + + @JsonProperty("y") + private String y; + + @JsonProperty("place_url") + private String placeUrl; + + @JsonProperty("road_address_name") + private String roadAddressName; + + @JsonProperty("phone") + private String phone; + + private Integer regionNum; +} diff --git a/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java new file mode 100644 index 0000000..2aa7f50 --- /dev/null +++ b/src/main/java/com/barogagi/kakaoplace/dto/KakaoPlaceSearchResDTO.java @@ -0,0 +1,19 @@ +package com.barogagi.kakaoplace.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "카카오 장소 추천 목록 결과 DTO") +public class KakaoPlaceSearchResDTO { + @JsonProperty("documents") + private List documents; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/naverblog/client/NaverBlogClient.java b/src/main/java/com/barogagi/naverblog/client/NaverBlogClient.java new file mode 100644 index 0000000..5975a87 --- /dev/null +++ b/src/main/java/com/barogagi/naverblog/client/NaverBlogClient.java @@ -0,0 +1,58 @@ +package com.barogagi.naverblog.client; + +import com.barogagi.naverblog.dto.NaverBlogResDTO; +import com.barogagi.naverblog.dto.NaverBlogSearchResDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import java.util.List; + +@Service +public class NaverBlogClient { + private static final Logger logger = LoggerFactory.getLogger(NaverBlogClient.class); + + @Value("${naver.x-naver-client-id}") + private String xNaverClientId; + + @Value("${naver.x-naver-client-secret}") + private String xNaverClientSecret; + + private final RestTemplate restTemplate = new RestTemplate(); + // https://openapi.naver.com/v1/search/blog.json?query=고궁의아침 강남구&display=5 + + public List searchNaverBlog(String query, int display) { + String url = String.valueOf(UriComponentsBuilder + .fromHttpUrl("https://openapi.naver.com/v1/search/blog.json") + .queryParam("query", query) + .queryParam("display", display) + .build(false)); +// logger.info("#$# url={}", url); + + HttpHeaders headers = new HttpHeaders(); + headers.set("X-Naver-Client-Id", xNaverClientId); + headers.set("X-Naver-Client-Secret", xNaverClientSecret); +// logger.info("#$# Request Headers: {}", headers); + + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + entity, + NaverBlogSearchResDTO.class + ); +// logger.info("#$# response.getBody()={}", response.getBody()); +// logger.info("#$# response.getBody().getItems()={}", response.getBody().getItems()); + List body = response.getBody().getItems(); + + return body; + } + +} diff --git a/src/main/java/com/barogagi/naverblog/dto/NaverBlogResDTO.java b/src/main/java/com/barogagi/naverblog/dto/NaverBlogResDTO.java new file mode 100644 index 0000000..7a45e83 --- /dev/null +++ b/src/main/java/com/barogagi/naverblog/dto/NaverBlogResDTO.java @@ -0,0 +1,33 @@ +package com.barogagi.naverblog.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +@Data +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "네이버 블로그 검색 결과 DTO") +public class NaverBlogResDTO { + @JsonProperty("title") + private String title; + + @JsonProperty("link") + private String link; + + @JsonProperty("description") + private String description; + + @JsonProperty("bloggername") + private String bloggerName; + + @JsonProperty("bloggerlink") + private String bloggerLink; + + @JsonProperty("postdate") + private String postDate; + +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/naverblog/dto/NaverBlogSearchResDTO.java b/src/main/java/com/barogagi/naverblog/dto/NaverBlogSearchResDTO.java new file mode 100644 index 0000000..6037548 --- /dev/null +++ b/src/main/java/com/barogagi/naverblog/dto/NaverBlogSearchResDTO.java @@ -0,0 +1,30 @@ +package com.barogagi.naverblog.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.util.List; + +@Getter +@ToString +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Schema(description = "네이버 블로그 검색 결과 DTO") +public class NaverBlogSearchResDTO { + @JsonProperty("lastBuildDate") + private String lastBuildDate; + + @JsonProperty("total") + private int total; + + @JsonProperty("start") + private int start; + + @JsonProperty("display") + private int display; + + @JsonProperty("items") + private List items; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/plan/command/entity/Plan.java b/src/main/java/com/barogagi/plan/command/entity/Plan.java index a824330..d5b7065 100644 --- a/src/main/java/com/barogagi/plan/command/entity/Plan.java +++ b/src/main/java/com/barogagi/plan/command/entity/Plan.java @@ -1,9 +1,19 @@ package com.barogagi.plan.command.entity; import com.barogagi.plan.command.ex_entity.PlanUserMembershipInfo; +import com.barogagi.region.command.entity.Place; +import com.barogagi.region.command.entity.PlanRegion; import com.barogagi.schedule.command.entity.Schedule; +import com.barogagi.tag.command.entity.PlanTag; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity @Getter @@ -27,6 +37,15 @@ public class Plan { @Column(name = "END_TIME") private String endTime; // 종료 시간 + @Column(name = "PLAN_LINK") + public String planLink; // 장소 링크 (이미지 불러오기용) + + @Column(name = "PLAN_DESCRIPTION") + private String planDescription; // 장소 한줄 설명(AI 생성) + + @Column(name = "PLAN_ADDRESS") + private String planAddress; // 장소 주소 + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "SCHEDULE_NUM", nullable = false) private Schedule schedule; // 일정 번호 @@ -38,4 +57,29 @@ public class Plan { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "ITEM_NUM", nullable = false) private Item item; // 아이템 번호 + + // PLACE와 1:1 mapping + @OneToOne(mappedBy = "plan", cascade = CascadeType.ALL, orphanRemoval = true) + private Place place; + + @CreationTimestamp + @Column(name = "REG_DATE", updatable = false) + private LocalDateTime regDate; // 등록일 + + @UpdateTimestamp + @Column(name = "UPD_DATE") + private LocalDateTime updDate; // 수정일 (업데이트 시 자동 변경) + + @Column(name = "DEL_YN", nullable = false, columnDefinition = "CHAR(1) DEFAULT 'N'") + private String delYn; // 삭제 여부(Y: 삭제, N: 미삭제) + + @OneToMany(mappedBy = "plan", cascade = CascadeType.REMOVE, orphanRemoval = true) + private List planRegions = new ArrayList<>(); + + @OneToMany(mappedBy = "plan", cascade = CascadeType.REMOVE, orphanRemoval = true) + private List planTags = new ArrayList<>(); + + public void markDeleted() { + this.delYn = "Y"; + } } diff --git a/src/main/java/com/barogagi/plan/command/repository/ItemRepository.java b/src/main/java/com/barogagi/plan/command/repository/ItemRepository.java new file mode 100644 index 0000000..1e6737f --- /dev/null +++ b/src/main/java/com/barogagi/plan/command/repository/ItemRepository.java @@ -0,0 +1,9 @@ +package com.barogagi.plan.command.repository; + +import com.barogagi.plan.command.entity.Item; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ItemRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/plan/command/repository/PlanRepository.java b/src/main/java/com/barogagi/plan/command/repository/PlanRepository.java index 278a505..afd27df 100644 --- a/src/main/java/com/barogagi/plan/command/repository/PlanRepository.java +++ b/src/main/java/com/barogagi/plan/command/repository/PlanRepository.java @@ -1,9 +1,14 @@ package com.barogagi.plan.command.repository; import com.barogagi.plan.command.entity.Plan; +import com.barogagi.schedule.command.entity.Schedule; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface PlanRepository extends JpaRepository { + + List findBySchedule(Schedule schedule); } diff --git a/src/main/java/com/barogagi/plan/command/repository/PlanTagRepository.java b/src/main/java/com/barogagi/plan/command/repository/PlanTagRepository.java new file mode 100644 index 0000000..a6a6027 --- /dev/null +++ b/src/main/java/com/barogagi/plan/command/repository/PlanTagRepository.java @@ -0,0 +1,7 @@ +package com.barogagi.plan.command.repository; + +import com.barogagi.tag.command.entity.PlanTag; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlanTagRepository extends JpaRepository { +} diff --git a/src/main/java/com/barogagi/plan/controller/PlaceController.java b/src/main/java/com/barogagi/plan/controller/PlaceController.java new file mode 100644 index 0000000..232f412 --- /dev/null +++ b/src/main/java/com/barogagi/plan/controller/PlaceController.java @@ -0,0 +1,57 @@ +package com.barogagi.plan.controller; + +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import com.barogagi.plan.query.service.PlaceQueryService; +import com.barogagi.response.ApiResponse; +import com.barogagi.schedule.controller.ScheduleController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "장소", description = "장소 관련 API") +@RestController +@RequestMapping("/place") +public class PlaceController { + + private static final Logger logger = LoggerFactory.getLogger(ScheduleController.class); + + private final PlaceQueryService placeQueryService; + private final String API_SECRET_KEY; + + public PlaceController(Environment environment, + PlaceQueryService placeQueryService) { + this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + this.placeQueryService = placeQueryService; + } + + @Operation(summary = "장소 검색 기능", + description = "사용자가 찾고 싶은 장소를 Kakao API로 검색하는 기능입니다.
" + + "- 일정을 생성하는 API를 호출할 때, 이 API에서 받은 placeName, placeUrl, addressName을 보내주세요.
" + + "- regionNum은 쓰지 않는 필드입니다. (null 값이 전달됨)") + @GetMapping("/keyword-search") + public ApiResponse searchPlace(@Parameter(description = "검색 키워드", example = "스타벅스") + @RequestParam String searchKeyword) { + + logger.info("CALL /place/keyword-search"); + logger.info("[input] searchKeyword={}", searchKeyword); + + List result; + try { + result = placeQueryService.searchPlace(searchKeyword); + + if (result == null) return ApiResponse.error("404", "장소 검색 실패"); + + } catch (Exception e) { + return ApiResponse.error("404", "장소 검색 실패"); + } + + + return ApiResponse.success(result, "장소 검색 성공"); + } +} diff --git a/src/main/java/com/barogagi/plan/dto/PlanDetailResDTO.java b/src/main/java/com/barogagi/plan/dto/PlanDetailResDTO.java deleted file mode 100644 index 1071f25..0000000 --- a/src/main/java/com/barogagi/plan/dto/PlanDetailResDTO.java +++ /dev/null @@ -1,20 +0,0 @@ -/* -package com.barogagi.plan.dto; - -public class PlanDetailResDTO { - private String planNm; // 계획명 - private String startTime; // 시작시간 - private String endTime; // 종료시간 - - private String regionNm; // 지역명 - - // 아이템(구분) - - // Figma에 표시되어 있는 값 - // - 아이콘 이미지 - // - 가게 등 대표 이미지 - - // Figma에 표시되어 있지 않은 값 - // - 태그 -} -*/ diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java index 1b096b8..d26b90b 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java @@ -15,9 +15,6 @@ @Schema(description = "계획 등록 요청 DTO") public class PlanRegistReqDTO { - @Schema(description = "계획 이름", example = "프랜차이즈카페") - public String planNm; - @Schema(description = "시작 시간", example = "08:00") public String startTime; @@ -30,15 +27,24 @@ public class PlanRegistReqDTO { @Schema(description = "카테고리 번호", example = "1") public int categoryNum; - @Schema(description = "추가 고려사항", example = "한식맛집") - private String comment; // 추가 고려사항 + @Schema(description = "지역 정보 DTO") + public List regionRegistReqDTOList; + + @Schema(description = "계획 태그 목록") + public List planTagRegistReqDTOList; + + // 사용자가 직접 세부일정을 추가한 경우에만 필요한 값 + @Schema(description = "사용자가 수동으로 추가한 일정인지 여부(AI 생성 안함)", example = "Y") + public String isUserAdded; + + @Schema(description = "사용자 직접 추가 CASE 1. 카카오 API 장소검색으로 추가한 장소 정보.") + UserAddedPlaceDTO userAddedPlaceDTO; + + @Schema(description = "사용자 직접 추가 CASE 2. 사용자가 직접 입력한 장소", example = "친구집 방문") + public String planNm; + - @Schema(description = "지역 정보 DTO") - public RegionRegistReqDTO regionRegistReqDTO; - @ArraySchema(schema = @Schema(implementation = TagRegistReqDTO.class), - arraySchema = @Schema(description = "태그 리스트", example = "[{\"tagNum\":1,\"tagNm\":\"이색카페\"},{\"tagNum\":2,\"tagNm\":\"맛집투어\"}]")) - public List tagRegistReqDTOList; } diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java new file mode 100644 index 0000000..69cf683 --- /dev/null +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java @@ -0,0 +1,62 @@ +package com.barogagi.plan.dto; + +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.tag.dto.TagRegistReqDTO; +import com.barogagi.tag.dto.TagRegistResDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +@Builder(toBuilder = true) +@Schema(description = "계획 등록 응답 DTO") +public class PlanRegistResDTO { + + @Schema(description = "시작 시간", example = "08:00") + public String startTime; + + @Schema(description = "종료 시간", example = "09:00") + public String endTime; + + @Schema(description = "아이템 번호", example = "1") + public int itemNum; + + @Schema(description = "아이템 명", example = "1") + public String itemNm; + + @Schema(description = "카테고리 번호", example = "1") + public int categoryNum; + + @Schema(description = "카테고리 명", example = "1") + public String categoryNm; + + @Schema(description = "장소 번호") + public Integer planNum; + + @Schema(description = "장소명") + public String planNm; + + @Schema(description = "장소 링크(이미지 불러오기용)") + public String planLink; + + @Schema(description = "장소 한줄 설명(ai 생성)") + public String planDescription; + + @Schema(description = "장소 주소") + public String planAddress; + + @Schema(description = "지역명") + public String regionNm; + + @Schema(description = "지역 번호") + public Integer regionNum; + + @Schema(description = "계획 태그 목록") + public List planTagRegistResDTOList; +} + diff --git a/src/main/java/com/barogagi/plan/dto/UserAddedPlaceDTO.java b/src/main/java/com/barogagi/plan/dto/UserAddedPlaceDTO.java new file mode 100644 index 0000000..3f43843 --- /dev/null +++ b/src/main/java/com/barogagi/plan/dto/UserAddedPlaceDTO.java @@ -0,0 +1,14 @@ +package com.barogagi.plan.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@Schema(description = "계획에 사용자가 수동으로 추가한 장소 정보 DTO") +public class UserAddedPlaceDTO { + private String placeName; + private String placeUrl; + private String addressName; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java b/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java new file mode 100644 index 0000000..f39d2bb --- /dev/null +++ b/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java @@ -0,0 +1,13 @@ +package com.barogagi.plan.query.mapper; + +import com.barogagi.plan.query.vo.PlanDetailVO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface CategoryMapper { + + // 카테고리명 조회 + String selectCategoryNmBy(int categoryNum); +} diff --git a/src/main/java/com/barogagi/plan/query/mapper/ItemMapper.java b/src/main/java/com/barogagi/plan/query/mapper/ItemMapper.java new file mode 100644 index 0000000..a0f4fa2 --- /dev/null +++ b/src/main/java/com/barogagi/plan/query/mapper/ItemMapper.java @@ -0,0 +1,10 @@ +package com.barogagi.plan.query.mapper; + +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ItemMapper { + + // 세부 카테고리(아이템)명 조회 + String selectItemNmBy(int itemNum); +} diff --git a/src/main/java/com/barogagi/plan/query/service/PlaceQueryService.java b/src/main/java/com/barogagi/plan/query/service/PlaceQueryService.java new file mode 100644 index 0000000..441a948 --- /dev/null +++ b/src/main/java/com/barogagi/plan/query/service/PlaceQueryService.java @@ -0,0 +1,25 @@ +package com.barogagi.plan.query.service; + +import com.barogagi.kakaoplace.client.KakaoPlaceClient; +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class PlaceQueryService { + private final KakaoPlaceClient kakaoPlaceClient; + + @Autowired + public PlaceQueryService(KakaoPlaceClient kakaoPlaceClient) { + this.kakaoPlaceClient = kakaoPlaceClient; + } + + public List searchPlace(String searchKeyword) { + + List searchKakaoPlaceList = + kakaoPlaceClient.searchKakaoPlaceByKeyword(searchKeyword); + return searchKakaoPlaceList; + } +} diff --git a/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java b/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java index 7312ee4..02a3e47 100644 --- a/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java +++ b/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java @@ -20,9 +20,6 @@ public PlanQueryService (PlanMapper planMapper) { } public List getPlanDetail(int scheduleNum) throws Exception{ - logger.info("scheduleNum={}", scheduleNum); - List result = planMapper.selectPlanDetailByScheduleNum(scheduleNum); - logger.info("result={}", result); - return result; + return planMapper.selectPlanDetailByScheduleNum(scheduleNum); } } diff --git a/src/main/java/com/barogagi/region/command/entity/Place.java b/src/main/java/com/barogagi/region/command/entity/Place.java new file mode 100644 index 0000000..1f5bc5a --- /dev/null +++ b/src/main/java/com/barogagi/region/command/entity/Place.java @@ -0,0 +1,40 @@ +package com.barogagi.region.command.entity; + +import com.barogagi.plan.command.entity.Plan; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Table(name = "PLACE") +public class Place { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "PLACE_NUM") + private Integer placeNum; + + @Column(name = "REGION_NM", nullable = false) + private String regionNm; + + @Column(name = "ADDRESS") + private String address; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "REGION_NUM") + private Region region; + + @Column(name = "PLAN_LINK") + private String planLink; + + @Column(name = "PLACE_DESCRIPTION") + private String placeDescription; + + // PLAN과 1:1 mapping + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "PLAN_NUM", unique = true) + private Plan plan; +} diff --git a/src/main/java/com/barogagi/region/command/entity/PlanRegion.java b/src/main/java/com/barogagi/region/command/entity/PlanRegion.java index 4250b1f..d4e6433 100644 --- a/src/main/java/com/barogagi/region/command/entity/PlanRegion.java +++ b/src/main/java/com/barogagi/region/command/entity/PlanRegion.java @@ -1,8 +1,7 @@ package com.barogagi.region.command.entity; -import jakarta.persistence.EmbeddedId; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; +import com.barogagi.plan.command.entity.Plan; +import jakarta.persistence.*; import lombok.*; @Entity @@ -14,5 +13,15 @@ public class PlanRegion { @EmbeddedId - private PlanRegionNum planRegionNum; // (복합키) 계획 번호, 지역 번호 + private PlanRegionId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("planNum") + @JoinColumn(name = "PLAN_NUM") + private Plan plan; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("regionNum") + @JoinColumn(name = "REGION_NUM") + private Region region; } diff --git a/src/main/java/com/barogagi/region/command/entity/PlanRegionId.java b/src/main/java/com/barogagi/region/command/entity/PlanRegionId.java new file mode 100644 index 0000000..ec048ea --- /dev/null +++ b/src/main/java/com/barogagi/region/command/entity/PlanRegionId.java @@ -0,0 +1,19 @@ +package com.barogagi.region.command.entity; + +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@Embeddable +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PlanRegionId implements Serializable { + private Integer regionNum; + private Integer planNum; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/region/command/entity/PlanRegionNum.java b/src/main/java/com/barogagi/region/command/entity/PlanRegionNum.java deleted file mode 100644 index 30d12ef..0000000 --- a/src/main/java/com/barogagi/region/command/entity/PlanRegionNum.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.barogagi.region.command.entity; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Embeddable -@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 -@AllArgsConstructor -public class PlanRegionNum { - // 계획별 지역 복합키 - - @Column(name="REGION_NUM") - private Integer regionNum; // 지역 번호 - - @Column(name="PLAN_NUM") - private Integer planNum; // 계획 변호 -} diff --git a/src/main/java/com/barogagi/region/command/repository/PlaceRepository.java b/src/main/java/com/barogagi/region/command/repository/PlaceRepository.java new file mode 100644 index 0000000..217127a --- /dev/null +++ b/src/main/java/com/barogagi/region/command/repository/PlaceRepository.java @@ -0,0 +1,7 @@ +package com.barogagi.region.command.repository; + +import com.barogagi.region.command.entity.Place; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlaceRepository extends JpaRepository { +} diff --git a/src/main/java/com/barogagi/region/command/repository/PlanRegionRepository.java b/src/main/java/com/barogagi/region/command/repository/PlanRegionRepository.java new file mode 100644 index 0000000..eddaedd --- /dev/null +++ b/src/main/java/com/barogagi/region/command/repository/PlanRegionRepository.java @@ -0,0 +1,7 @@ +package com.barogagi.region.command.repository; + +import com.barogagi.region.command.entity.PlanRegion; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PlanRegionRepository extends JpaRepository { +} diff --git a/src/main/java/com/barogagi/region/controller/RegionController.java b/src/main/java/com/barogagi/region/controller/RegionController.java index 205afb4..8a2312e 100644 --- a/src/main/java/com/barogagi/region/controller/RegionController.java +++ b/src/main/java/com/barogagi/region/controller/RegionController.java @@ -1,9 +1,115 @@ package com.barogagi.region.controller; +import com.barogagi.plan.query.service.PlanQueryService; +import com.barogagi.region.dto.RegionGeoCodeResDTO; +import com.barogagi.region.dto.RegionSearchResDTO; +import com.barogagi.region.query.service.RegionGeoCodeService; +import com.barogagi.region.query.service.RegionQueryService; +import com.barogagi.response.ApiResponse; +import com.barogagi.schedule.command.service.ScheduleCommandService; +import com.barogagi.schedule.controller.ScheduleController; +import com.barogagi.schedule.dto.ScheduleDetailResDTO; +import com.barogagi.schedule.query.service.ScheduleQueryService; +import com.barogagi.util.InputValidate; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + +@Tag(name = "지역", description = "지역 관련 API") @RestController @RequestMapping("/region") public class RegionController { + private static final Logger logger = LoggerFactory.getLogger(RegionController.class); + + private final InputValidate inputValidate; + + private final RegionGeoCodeService regionGeoCodeService; + + private final RegionQueryService regionQueryService; + + private final String API_SECRET_KEY; + + public RegionController(Environment environment, + InputValidate inputValidate, + RegionGeoCodeService regionGeoCodeService, + RegionQueryService regionQueryService) { + this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + this.inputValidate = inputValidate; + this.regionGeoCodeService = regionGeoCodeService; + this.regionQueryService = regionQueryService; + } + @Operation(summary = "주소를 x,y 좌표로 변환하는 기능", description = "주소 번호를 받아서 x,y 좌표로 변환하는 기능입니다") + @GetMapping("/geocode") + public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 주소 번호", example = "1") @RequestParam Integer regionNum) { + + logger.info("CALL /region/geocode"); + logger.info("[input] regionNum={}", regionNum); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ + if (true) { + + // TODO. 에러 메시지 정의하기 + if (inputValidate.isInvalidInteger(regionNum)) { + resultCode = "101"; + message = "좌표로 변환할 법정동/행정동 번호가 숫자가 아닙니다."; + } else { + + RegionGeoCodeResDTO result = regionGeoCodeService.getGeocode(regionNum); + logger.info("result={}", result.toString()); + + if (result == null) { + resultCode = "300"; + message = "조회할 지역이 존재하지 않습니다."; // TODO. 에러 메시지 정의하기 + + } else { + resultCode = "200"; + message = "주소 좌표 변환 성공"; + apiResponse.setData(result); + } + } + + } else { + resultCode = "100"; + message = "잘못된 접근입니다."; + } + } catch (Exception e) { + resultCode = "400"; + message = "오류가 발생하였습니다."; + throw new RuntimeException(e); + + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); + } + return apiResponse; + } + + @Operation(summary = "주소 목록을 검색하는 기능", description = "주소 목록을 검색하는 기능입니다.
" + + "REGION table에 저장된 값 중, level 1부터 4까지 가장 정확도 높은 순으로 지역명 최대 10개를 리턴합니다.
" + + "행정구역 단계(시/도, 시/군/구, 동/면/리)를 조합하여 결과를 반환하며, 중복되는 상위 주소(예: '서울특별시 강남구')는 한 번만 표시됩니다.") + @GetMapping("/searchList") + public ApiResponse searchList(@Parameter(description = "검색할 주소명", example = "강남") @RequestParam String regionQuery) { + + logger.info("CALL /region/searchList"); + logger.info("[input] regionQuery={}", regionQuery); + + List result = regionQueryService.searchList(regionQuery); + return ApiResponse.success(result, "주소 목록 검색 성공"); + + } } diff --git a/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java b/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java new file mode 100644 index 0000000..d80660f --- /dev/null +++ b/src/main/java/com/barogagi/region/dto/RegionGeoCodeResDTO.java @@ -0,0 +1,27 @@ +package com.barogagi.region.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder(toBuilder = true) +@ToString +public class RegionGeoCodeResDTO { + @Schema(description = "x 좌표", example = "127.04892851392") + public String x; + + @Schema(description = "y 좌표", example = "37.5091105328378") + public String y; + + public int regionNum; + + public String regionLevel1; + + public String regionLevel2; + + public String regionLevel3; + + public String regionLevel4; +} diff --git a/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java b/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java index f31eedc..9225d41 100644 --- a/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java +++ b/src/main/java/com/barogagi/region/dto/RegionRegistReqDTO.java @@ -1,24 +1,30 @@ package com.barogagi.region.dto; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; import lombok.Getter; import lombok.ToString; @Getter @ToString @Schema(description = "지역 정보 DTO") +@Builder(toBuilder = true) public class RegionRegistReqDTO { - // 카카오 api에서 지역 데이터 어떻게 넘겨주는지 확인 필요 +// @Schema(description = "지역명 대분류", example = "서울특별시") +// public String regionNm1; - @Schema(description = "지역명 대분류", example = "서울특별시") - public String regionNm1; +// @Schema(description = "지역명 소분류", example = "강남구") +// public String regionNm2; - @Schema(description = "지역명 소분류", example = "강남구") - public String regionNm2; +// @Schema(description = "x 좌표", example = "127.04892851392") +// public String x; -// public int regionNum; // 지역 번호 -// public String regionLevel1; // 대분류 -// public String regionLevel2; // 시/군 -// public String regionLevel3; // 구 -// public String regionLevel4; // 동/면/리 +// @Schema(description = "y 좌표", example = "37.5091105328378") +// public String y; + + public int regionNum; // 지역 번호 + public String regionLevel1; // 대분류 + public String regionLevel2; // 시/군 + public String regionLevel3; // 구 + public String regionLevel4; // 동/면/리 } diff --git a/src/main/java/com/barogagi/region/dto/RegionSearchResDTO.java b/src/main/java/com/barogagi/region/dto/RegionSearchResDTO.java new file mode 100644 index 0000000..1e61e5f --- /dev/null +++ b/src/main/java/com/barogagi/region/dto/RegionSearchResDTO.java @@ -0,0 +1,14 @@ +package com.barogagi.region.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder(toBuilder = true) +@ToString +public class RegionSearchResDTO { + public String regionNm; + public Integer regionNum; +} diff --git a/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java b/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java index 0f70e5c..f714a7b 100644 --- a/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java +++ b/src/main/java/com/barogagi/region/query/mapper/RegionMapper.java @@ -10,4 +10,21 @@ public interface RegionMapper { // 계획 상세 조회 - 지역 상세 조회 List selectRegionByPlanNum(int planNum); + + // 지역 번호로 지역명 조회 + RegionDetailVO selectRegionByRegionNum(int regionNum); + + // 키워드로 지역 목록 검색 - level 1부터 가장 정확도 높은 순으로 10개 리턴 + List selectRegionByRegionNm(String regionQuery); + + // 지역 번호로 지역명 조회 (단순 String 반환) +// String selectRegionNameByRegionNum(int regionNum); + + RegionDetailVO selectRegionByLevel4(String level4); + + RegionDetailVO selectRegionByLevel3(String level3); + + RegionDetailVO selectRegionByLevel2(String level2); + + RegionDetailVO selectRegionByLevel1(String level1); } diff --git a/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java b/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java new file mode 100644 index 0000000..51f255f --- /dev/null +++ b/src/main/java/com/barogagi/region/query/service/RegionGeoCodeService.java @@ -0,0 +1,79 @@ +package com.barogagi.region.query.service; + +import com.barogagi.kakaoplace.client.KakaoGeoCodeClient; +import com.barogagi.kakaoplace.dto.KakaoGeoCodeResDTO; +import com.barogagi.region.dto.RegionGeoCodeResDTO; +import com.barogagi.region.query.mapper.RegionMapper; +import com.barogagi.region.query.vo.RegionDetailVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Service +public class RegionGeoCodeService { + private static final Logger logger = LoggerFactory.getLogger(RegionGeoCodeService.class); + + private final KakaoGeoCodeClient kakaoGeoCodeClient; + + private final RegionMapper regionMapper; + + @Autowired + public RegionGeoCodeService(KakaoGeoCodeClient kakaoGeoCodeClient, + RegionMapper regionMapper) { + this.kakaoGeoCodeClient = kakaoGeoCodeClient; + this.regionMapper = regionMapper; + } + + /** + * 주소를 받아서 Kakao API로 좌표(x, y) 변환 + */ + public RegionGeoCodeResDTO getGeocode(Integer regionNum) { + + RegionDetailVO region = regionMapper.selectRegionByRegionNum(regionNum); + + if (region == null) { // todo. 에러 처리 필요 + return null; + } + + // 2. address 문자열 조립 (null/빈 값 무시) + String address = Stream.of( + region.getRegionLevel1(), + region.getRegionLevel2(), + region.getRegionLevel3(), + region.getRegionLevel4() + ) + .filter(Objects::nonNull) + .filter(s -> !s.isBlank()) + .collect(Collectors.joining(" ")); + + logger.info("address = {}", address); + + + // 3. 카카오 API 호출 + List result = kakaoGeoCodeClient.convertKakaoGeoCode(address); + + if (result.isEmpty()) { + return null; + } + + // 4. documents 배열 중 첫 번째 좌표를 DTO에 담아서 리턴 + KakaoGeoCodeResDTO first = result.get(0); + + return RegionGeoCodeResDTO.builder() + .x(first.getX()) + .y(first.getY()) + .regionNum(region.getRegionNum()) + .regionLevel1(region.getRegionLevel1()) + .regionLevel2(region.getRegionLevel2()) + .regionLevel3(region.getRegionLevel3()) + .regionLevel4(region.getRegionLevel4()) + .build(); + } +} + diff --git a/src/main/java/com/barogagi/region/query/service/RegionQueryService.java b/src/main/java/com/barogagi/region/query/service/RegionQueryService.java index 40f4903..ee64460 100644 --- a/src/main/java/com/barogagi/region/query/service/RegionQueryService.java +++ b/src/main/java/com/barogagi/region/query/service/RegionQueryService.java @@ -1,7 +1,151 @@ package com.barogagi.region.query.service; +import com.barogagi.region.dto.RegionSearchResDTO; +import com.barogagi.region.query.mapper.RegionMapper; +import com.barogagi.region.query.vo.RegionDetailVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.*; +import java.util.stream.Collectors; + @Service public class RegionQueryService { + private static final Logger logger = LoggerFactory.getLogger(RegionQueryService.class); + + private final RegionMapper regionMapper; + + @Autowired + public RegionQueryService(RegionMapper regionMapper) { + this.regionMapper = regionMapper; + } + + /** + * 검색어로 지역 정보를 조회하고, + * 각 지역의 행정구역 단계별 주소를 RegionSearchResDTO 리스트로 반환한다. + * + * 예시: + * - CASE 1: 서울특별시 / null / 강남구 / 역삼동 + * → "서울특별시 강남구" + * → "서울특별시 강남구 역삼동" + * + * - CASE 2: 경기도 / 안양시 / 만안구 / 석수동 + * → "경기도 안양시" + * → "경기도 안양시 만안구" + * → "경기도 안양시 만안구 석수동" + * + * - CASE 3: 울산광역시 / 울주군 / null / 온산읍 + * → "울산광역시 울주군" + * → "울산광역시 울주군 온산읍" + * + * @param regionQuery 검색 키워드 + * @return RegionSearchResDTO 리스트 (단계별 주소 포함) + */ + public List searchList(String regionQuery) { + List regionList = regionMapper.selectRegionByRegionNm(regionQuery); + + List result = new ArrayList<>(); + Set seen = new HashSet<>(); // 중복 방지 + + for (RegionDetailVO r : regionList) { + List parts = new ArrayList<>(); + + if (r.getRegionLevel1() != null && !r.getRegionLevel1().isBlank()) { + parts.add(r.getRegionLevel1()); + } + if (r.getRegionLevel2() != null && !r.getRegionLevel2().isBlank()) { + parts.add(r.getRegionLevel2()); + } + if (r.getRegionLevel3() != null && !r.getRegionLevel3().isBlank()) { + parts.add(r.getRegionLevel3()); + } + + // 상위 주소 (레벨1~레벨3까지) → 중복 방지 후 추가 + String upperAddress = String.join(" ", parts); + if (!upperAddress.isBlank() && seen.add(upperAddress)) { + result.add(RegionSearchResDTO.builder() + .regionNum(r.getRegionNum()) + .regionNm(upperAddress) + .build()); + } + + // 레벨4 (동/면/리) + if (r.getRegionLevel4() != null && !r.getRegionLevel4().isBlank()) { + String fullAddress = upperAddress + " " + r.getRegionLevel4(); + if (seen.add(fullAddress)) { + result.add(RegionSearchResDTO.builder() + .regionNum(r.getRegionNum()) + .regionNm(fullAddress) + .build()); + } + } + } + + return result; + } + + public RegionDetailVO getRegionByRegionNum(int regionNum) { + return regionMapper.selectRegionByRegionNum(regionNum); + } + + public RegionDetailVO getRegionNumByAddress(String address) { + + // todo. !!!!!!!!!! 주소가 이상하게 들어감 !!!!!!!! + if (address == null || address.isBlank()) { + throw new IllegalArgumentException("주소가 입력되지 않았습니다."); + } + + String[] parts = address.split(" "); + + List tokens = Arrays.stream(parts) + .filter(t -> !t.matches("\\d+")) + .collect(Collectors.toList()); + + // LEVEL 4 (동/읍/면) + if (tokens.size() >= 3) { + String level4 = tokens.get(2); + RegionDetailVO r4 = regionMapper.selectRegionByLevel4(level4); + logger.info("#$# r4 = {}", r4); + + if (r4 != null) { + return r4; + } + } + logger.info("#$# next?"); + + // LEVEL 3 + if (tokens.size() >= 3) { + String level3 = tokens.get(2); + RegionDetailVO r3 = regionMapper.selectRegionByLevel3(level3); + logger.info("#$# r3 = {}", r3); + + if (r3 != null) { + return r3; + } + } + + // LEVEL 2 + if (tokens.size() >= 2) { + String level2 = tokens.get(1); + RegionDetailVO r2 = regionMapper.selectRegionByLevel2(level2); + if (r2 != null) { + return r2; + } + } + + // LEVEL 1 + if (tokens.size() >= 1) { + String level1 = tokens.get(0); + RegionDetailVO r1 = regionMapper.selectRegionByLevel1(level1); + if (r1 != null) { + return r1; + } + } + + throw new IllegalStateException("입력된 주소로 지역을 찾을 수 없습니다: " + address); + } + } + diff --git a/src/main/java/com/barogagi/response/ApiResponse.java b/src/main/java/com/barogagi/response/ApiResponse.java index 5568d6b..cb261b9 100644 --- a/src/main/java/com/barogagi/response/ApiResponse.java +++ b/src/main/java/com/barogagi/response/ApiResponse.java @@ -15,4 +15,19 @@ public class ApiResponse { // 데이터 private T data; + + public static ApiResponse success(T data, String message) { + ApiResponse res = new ApiResponse<>(); + res.resultCode = "200"; + res.message = message; + res.data = data; + return res; + } + + public static ApiResponse error(String code, String message) { + ApiResponse res = new ApiResponse<>(); + res.resultCode = code; + res.message = message; + return res; + } } diff --git a/src/main/java/com/barogagi/schedule/command/entity/Schedule.java b/src/main/java/com/barogagi/schedule/command/entity/Schedule.java index 7d5c65c..2a26c66 100644 --- a/src/main/java/com/barogagi/schedule/command/entity/Schedule.java +++ b/src/main/java/com/barogagi/schedule/command/entity/Schedule.java @@ -1,7 +1,14 @@ package com.barogagi.schedule.command.entity; +import com.barogagi.plan.command.entity.Plan; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity @Getter @@ -28,6 +35,30 @@ public class Schedule { @Column(name = "END_DATE", nullable = false) private String endDate; // 종료 날짜 - @Column(name = "RADIUS", nullable = false) - private int radius; // 추천 반경 (미터 단위) +// @Column(name = "RADIUS", nullable = false) +// private int radius; // 추천 반경 (미터 단위) + + @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true) + private List plans = new ArrayList<>(); + + @CreationTimestamp + @Column(name = "REG_DATE", updatable = false) + private LocalDateTime regDate; // 등록일 + + @UpdateTimestamp + @Column(name = "UPD_DATE") + private LocalDateTime updDate; // 수정일 (업데이트 시 자동 변경) + + @Column(name = "DEL_YN", nullable = false, columnDefinition = "CHAR(1) DEFAULT 'N'") + private String delYn; // 삭제 여부(Y: 삭제, N: 미삭제) + + public void markDeleted() { + this.delYn = "Y"; + } + + public void updateBasicInfo(String scheduleNm, String startDate, String endDate) { + this.scheduleNm = scheduleNm; + this.startDate = startDate; + this.endDate = endDate; + } } \ No newline at end of file diff --git a/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java b/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java index 5ea08be..659b660 100644 --- a/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java +++ b/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java @@ -4,6 +4,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface ScheduleRepository extends JpaRepository{ + Optional findByScheduleNumAndMembershipNo(Integer scheduleNum, Integer membershipNo); } \ No newline at end of file diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 77b867c..bbd5960 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -1,7 +1,686 @@ package com.barogagi.schedule.command.service; +import com.barogagi.ai.client.AIClient; +import com.barogagi.ai.dto.AIReqDTO; +import com.barogagi.ai.dto.AIReqWrapper; +import com.barogagi.ai.dto.AIResDTO; +import com.barogagi.kakaoplace.client.KakaoPlaceClient; +import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import com.barogagi.naverblog.client.NaverBlogClient; +import com.barogagi.naverblog.dto.NaverBlogResDTO; +import com.barogagi.plan.command.entity.Item; +import com.barogagi.plan.command.entity.Plan; +import com.barogagi.plan.command.ex_entity.PlanUserMembershipInfo; +import com.barogagi.plan.command.repository.ItemRepository; +import com.barogagi.plan.command.repository.PlanRepository; +import com.barogagi.plan.command.repository.PlanTagRepository; +import com.barogagi.plan.dto.PlanRegistReqDTO; +import com.barogagi.plan.dto.PlanRegistResDTO; +import com.barogagi.plan.dto.UserAddedPlaceDTO; +import com.barogagi.plan.query.mapper.CategoryMapper; +import com.barogagi.plan.query.mapper.ItemMapper; +import com.barogagi.region.command.entity.Place; +import com.barogagi.region.command.entity.PlanRegion; +import com.barogagi.region.command.entity.PlanRegionId; +import com.barogagi.region.command.entity.Region; +import com.barogagi.region.command.repository.PlaceRepository; +import com.barogagi.region.command.repository.PlanRegionRepository; +import com.barogagi.region.command.repository.RegionRepository; +import com.barogagi.region.dto.RegionGeoCodeResDTO; +import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.region.dto.RegionSearchResDTO; +import com.barogagi.region.query.service.RegionGeoCodeService; +import com.barogagi.region.query.service.RegionQueryService; +import com.barogagi.region.query.vo.RegionDetailVO; +import com.barogagi.schedule.command.entity.Schedule; +import com.barogagi.schedule.command.repository.ScheduleRepository; +import com.barogagi.schedule.dto.ScheduleRegistReqDTO; +import com.barogagi.schedule.dto.ScheduleRegistResDTO; +import com.barogagi.tag.command.entity.*; +import com.barogagi.tag.command.repository.ScheduleTagRepository; +import com.barogagi.tag.command.repository.TagRepository; +import com.barogagi.tag.dto.TagRegistReqDTO; +import com.barogagi.tag.dto.TagRegistResDTO; +import com.barogagi.tag.query.service.TagQueryService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +import static com.barogagi.util.HtmlUtils.stripHtml; @Service public class ScheduleCommandService { + private static final Logger logger = LoggerFactory.getLogger(ScheduleCommandService.class); + + private final CategoryMapper categoryMapper; + private final ItemMapper itemMapper; + private final KakaoPlaceClient kakaoPlaceClient; + private final NaverBlogClient naverBlogClient; + private final AIClient aiClient; + + private final TagQueryService tagQueryService; + private final RegionGeoCodeService regionGeoCodeService; + + private final ScheduleRepository scheduleRepository; + private final ScheduleTagRepository scheduleTagRepository; + private final TagRepository tagRepository; + private final ItemRepository itemRepository; + private final PlanRepository planRepository; + private final PlanTagRepository planTagRepository; + private final RegionRepository regionRepository; + private final PlanRegionRepository planRegionRepository; + private final PlaceRepository placeRepository; + private final RegionQueryService regionQueryService ; + + + @Value("${kakao.radius}") + private int radius; + + @Value("${naver.display}") + private int naverBlogDisplay; + + @Autowired + public ScheduleCommandService(CategoryMapper categoryMapper, ItemMapper itemMapper, + KakaoPlaceClient kakaoPlaceClient, NaverBlogClient naverBlogClient, + AIClient aiClient, TagQueryService tagQueryService, RegionGeoCodeService regionGeoCodeService, + ScheduleRepository scheduleRepository, ScheduleTagRepository scheduleTagRepository, + TagRepository tagRepository, ItemRepository itemRepository, + PlanRepository planRepository, PlanTagRepository planTagRepository, + RegionRepository regionRepository, PlanRegionRepository planRegionRepository, + PlaceRepository placeRepository, RegionQueryService regionQueryService) { + this.itemMapper = itemMapper; + this.categoryMapper = categoryMapper; + this.kakaoPlaceClient = kakaoPlaceClient; + this.naverBlogClient = naverBlogClient; + this.aiClient = aiClient; + this.tagQueryService = tagQueryService; + this.regionGeoCodeService = regionGeoCodeService; + this.scheduleRepository = scheduleRepository; + this.scheduleTagRepository = scheduleTagRepository; + this.tagRepository = tagRepository; + this.itemRepository = itemRepository; + this.planRepository = planRepository; + this.planTagRepository = planTagRepository; + this.regionRepository = regionRepository; + this.planRegionRepository = planRegionRepository; + this.placeRepository = placeRepository; + this.regionQueryService = regionQueryService; + } + + + + public ScheduleRegistResDTO createSchedule(ScheduleRegistReqDTO scheduleRegistReqDTO) { + + List planResList = new ArrayList<>(); + + // 스케줄 공통 정보 + String scheduleNm = scheduleRegistReqDTO.getScheduleNm(); + String startDate = scheduleRegistReqDTO.getStartDate(); + String endDate = scheduleRegistReqDTO.getEndDate(); + + + scheduleRegistReqDTO.getPlanRegistReqDTOList().forEach(plan -> { + if (plan.getPlanTagRegistReqDTOList() != null) { + plan.getPlanTagRegistReqDTOList().forEach(tag -> { + logger.info("tagNum={}, tagNm={}", tag.getTagNum(), tag.getTagNm()); + }); + } else { + logger.info("태그 없음"); + } + }); + + for (PlanRegistReqDTO plan : scheduleRegistReqDTO.getPlanRegistReqDTOList()) { + + if (plan.getIsUserAdded().equals("Y")) { + // ➜ A. 사용자가 직접 입력한 플랜 + logger.info("#$# ➜ A. 사용자가 직접 입력한 플랜 plan={}", plan); + PlanRegistResDTO planRes = handleUserPlan(plan); + planResList.add(planRes); + + } else { + // ➜ B. AI가 추천해줘야 하는 플랜 + logger.info("#$# ➜ B. AI가 추천해줘야 하는 플랜 plan={}", plan); + PlanRegistResDTO planRes = handleAIPlan(scheduleRegistReqDTO, plan); + planResList.add(planRes); + } + logger.info("#$# for문 도는중 planResList={}", planResList); + + + } + + // ---------- 6) ScheduleRegistResDTO 묶어서 리턴 ---------- + return ScheduleRegistResDTO.builder() + .scheduleNm(scheduleNm) + .startDate(startDate) + .endDate(endDate) + .planRegistResDTOList(planResList) + .build(); + } + + private PlanRegistResDTO handleAIPlan(ScheduleRegistReqDTO scheduleRegistReqDTO, PlanRegistReqDTO plan) { + // ---------- 1) 지역 번호로 x, y 좌표 검색 & Kakao 후보장소 수집(평탄화) ---------- + if (plan.getRegionRegistReqDTOList() == null || plan.getRegionRegistReqDTOList().isEmpty()) { + logger.info("#$# skip: plan has no regions. plan={}", plan); + return null; + } + + int limitPlace = calLimitPlace(plan.getRegionRegistReqDTOList().size()); + + List> allKakaoPlaceResults = new ArrayList<>(); + + String categoryNm = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); + String queryString = categoryNm; // 검색어 + + String itemNm = itemMapper.selectItemNmBy(plan.getItemNum()); // todo. itemNm을 검색어로 쓸지 고려하기 + + for (RegionRegistReqDTO region : plan.getRegionRegistReqDTOList()) { + // regionNum으로 좌표 가져오기 + RegionGeoCodeResDTO geo = regionGeoCodeService.getGeocode(region.getRegionNum()); + if (geo == null) { + logger.warn("#$# regionNum={} not found in DB, skip.", region.getRegionNum()); + continue; + } + + RegionRegistReqDTO updatedRegion = region.toBuilder() + .regionLevel1(geo.getRegionLevel1()) + .regionLevel2(geo.getRegionLevel2()) + .regionLevel3(geo.getRegionLevel3()) + .regionLevel4(geo.getRegionLevel4()) + .build(); + + // 리스트 교체 + int idx = plan.getRegionRegistReqDTOList().indexOf(region); + plan.getRegionRegistReqDTOList().set(idx, updatedRegion); + + // 지역명 결정 (레벨2/3 우선순위 적용) + String regionName = null; + if (updatedRegion.getRegionLevel3() != null && !updatedRegion.getRegionLevel3().isEmpty()) { + regionName = updatedRegion.getRegionLevel3(); + } else if (updatedRegion.getRegionLevel2() != null && !updatedRegion.getRegionLevel2().isEmpty()) { + regionName = updatedRegion.getRegionLevel2(); + } + + List oneRegionPlaces = + kakaoPlaceClient.searchKakaoPlace(queryString, geo.getX(), geo.getY(), radius, limitPlace); + allKakaoPlaceResults.add(oneRegionPlaces); + + // 각 장소에 regionNum 세팅 + if (oneRegionPlaces != null) { + oneRegionPlaces.forEach(k -> k.setRegionNum(region.getRegionNum())); + } + + logger.info("#$# resolved regionName={} for regionNum={}", regionName, updatedRegion.getRegionNum()); + + } + + logger.info("allKakaoPlaceResults: {}", allKakaoPlaceResults); + + + // Kakao 평탄화(이 순서를 기준으로 이후 Naver/AI도 동일하게 맞춤) + List flatKakao = allKakaoPlaceResults.stream() + .filter(Objects::nonNull) + .flatMap(List::stream) + .collect(Collectors.toList()); + + if (flatKakao.isEmpty()) { + logger.info("#$# no kakao results. plan={}", plan); + return null; + } + + // ---------- 2) Naver 블로그로 title/description 만들기 ---------- + List allBlogsFlat = new ArrayList<>(); + for (KakaoPlaceResDTO k : flatKakao) { + String query = k.getPlaceName() + " " + k.getRoadAddressName(); + List blogs = naverBlogClient.searchNaverBlog(query, naverBlogDisplay); + if (blogs != null) { + allBlogsFlat.addAll(blogs); + } + } + logger.info("allNaverBlogResults.size={}", allBlogsFlat.size()); + + // AI placeList: Kakao 후보 1:1이 가장 안전하지만 현재는 blog기반으로 작성 + // 블로그가 없을 때를 대비해서 Kakao 기본 설명을 fallback으로 변경 + List placeList = new ArrayList<>(); + for (int i = 0; i < flatKakao.size(); i++) { + KakaoPlaceResDTO k = flatKakao.get(i); + // 대응되는 블로그가 없다면 간단한 설명을 생성(fallback) + String title = k.getPlaceName(); + String desc = Optional.ofNullable(k.getCategoryGroupName()).orElse("카테고리 정보 없음") + + " · " + Optional.ofNullable(k.getRoadAddressName()).orElse(k.getAddressName()); + + // 블로그 결과가 있다면 맨 앞 하나만 사용(원한다면 점수화/요약 로직 확장) + if (i < allBlogsFlat.size()) { + NaverBlogResDTO b = allBlogsFlat.get(i); + title = stripHtml(b.getTitle()); + desc = stripHtml(b.getDescription()); + } + + placeList.add(AIReqDTO.builder() + .title(title) + .description(desc) + .build()); + } + + // ---------- 3) AI 호출 ---------- + // todo. 일정 전체에 대한 태그(schedulePlanTagRegistReqDTOList)도 참고하도록 수정해야 함 + List tagNums = Optional.ofNullable(plan.getPlanTagRegistReqDTOList()) + .orElseGet(List::of) + .stream() + .map(TagRegistReqDTO::getTagNum) + .collect(Collectors.toList()); + + List tagNames = tagQueryService.findTagNmByTagNum(tagNums); + + AIReqWrapper aiReqWrapper = AIReqWrapper.builder() + .tags(tagNames) + .comment(Optional.ofNullable(scheduleRegistReqDTO.getComment()).orElse("")) + .placeList(placeList) + .build(); + + AIResDTO aiRes = aiClient.recommandPlace(aiReqWrapper); + + // ---------- 4) AI가 고른 index → Kakao place 선택 ---------- + Integer idx = (aiRes != null) ? aiRes.getRecommandPlaceIndex() : null; + if (idx == null || idx < 0 || idx >= flatKakao.size()) { + idx = 0; // fallback + } + KakaoPlaceResDTO aiChosen = flatKakao.get(idx); + + // ---------- 5) 응답 DTO 생성 ---------- + String regionNm = null; + if (aiChosen.getRegionNum() != null) { + regionNm = regionRepository.findById(aiChosen.getRegionNum()) + .map(region -> { + // 지역명은 보통 3레벨 > 2레벨 순으로 선택 + if (region.getRegionLevel3() != null && !region.getRegionLevel3().isEmpty()) + return region.getRegionLevel3(); + else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty()) + return region.getRegionLevel2(); + else + return region.getRegionLevel1(); + }) + .orElse(null); + } + + return PlanRegistResDTO.builder() + .startTime(plan.getStartTime()) + .endTime(plan.getEndTime()) + .planNm(aiChosen.getPlaceName()) + .planLink(aiChosen.getPlaceUrl()) + .planDescription(aiRes != null ? aiRes.getAiDescription() : null) + .planAddress(Optional.ofNullable(aiChosen.getRoadAddressName()).orElse(aiChosen.getAddressName())) + .regionNm(regionNm) + .regionNum(aiChosen.getRegionNum()) + .categoryNm(categoryNm) + .categoryNum(plan.getCategoryNum()) + .itemNm(itemNm) + .itemNum(plan.getItemNum()) + .planTagRegistResDTOList( + Optional.ofNullable(plan.getPlanTagRegistReqDTOList()) + .orElseGet(List::of) + .stream() + .map(tagReq -> TagRegistResDTO.builder() + .tagNum(tagReq.getTagNum()) + .tagNm(tagReq.getTagNm()) + .build()) + .collect(Collectors.toList()) + ) + // .aiChosen(aiChosen) + .build(); + } + + + private PlanRegistResDTO handleUserPlan(PlanRegistReqDTO plan) { + + // CASE 1: 카카오 장소 ID로 선택한 경우 + if (plan.getUserAddedPlaceDTO() != null) { + logger.info("#$# ➜ A-1. 사용자가 직접 입력한 플랜 plan={}", plan); + UserAddedPlaceDTO userAddedPlaceDTO = plan.getUserAddedPlaceDTO(); + + RegionDetailVO regionDetailVO = regionQueryService.getRegionNumByAddress(userAddedPlaceDTO.getAddressName()); + String regionNm = resolveRegionName(regionDetailVO); + + logger.info("#$# 사용자가 선택한 장소 userAddedPlaceDTO addressName={}, resolved regionNum={}", userAddedPlaceDTO.getAddressName(), regionDetailVO.getRegionNum()); + + return PlanRegistResDTO.builder() + .startTime(plan.getStartTime()) + .endTime(plan.getEndTime()) + .planNm(userAddedPlaceDTO.getPlaceName()) + .planLink(userAddedPlaceDTO.getPlaceUrl()) + .planDescription(null) // 사용자가 추가한 일정은 설명 없음 + .planAddress(userAddedPlaceDTO.getAddressName()) + .regionNum(regionDetailVO.getRegionNum()) //todo. check + .regionNm(regionNm) //todo. check + .categoryNum(plan.getCategoryNum()) + .categoryNm(categoryMapper.selectCategoryNmBy(plan.getCategoryNum())) + .itemNum(plan.getItemNum()) + .itemNm(itemMapper.selectItemNmBy(plan.getItemNum())) + .planTagRegistResDTOList(List.of()) // 사용자 일정은 태그 없음 + .build(); + } else { + + // CASE 2: 텍스트로 직접 입력한 경우 + logger.info("#$# ➜ A-2. 사용자가 직접 입력한 플랜 plan={}", plan); + + logger.info("#$# plan.getRegionRegistReqDTOList().get(0).getRegionNum()={}", plan.getRegionRegistReqDTOList().get(0).getRegionNum()); + RegionDetailVO region = regionQueryService.getRegionByRegionNum(plan.getRegionRegistReqDTOList().get(0).getRegionNum()); + logger.info("#$# regionNm={} {}", region.getRegionLevel2(), region.getRegionLevel3()); + + return PlanRegistResDTO.builder() + .startTime(plan.getStartTime()) + .endTime(plan.getEndTime()) + .planNm(plan.getPlanNm()) + .planAddress(null) + .planLink(null) + .categoryNum(plan.getCategoryNum()) + .categoryNm(categoryMapper.selectCategoryNmBy(plan.getCategoryNum())) + .itemNum(plan.getItemNum()) + .itemNm(itemMapper.selectItemNmBy(plan.getItemNum())) + .regionNum(plan.getRegionRegistReqDTOList().get(0).getRegionNum()) + .regionNm(region.getRegionLevel3() != null ? region.getRegionLevel3() : region.getRegionLevel2()) + .planTagRegistResDTOList(List.of()) // 사용자 입력은 태그 없음 + .build(); + } + + } + + // 등록완료된 스케쥴의 num을 리턴 + public Integer saveSchedule(ScheduleRegistResDTO scheduleRegistResDTO) { + return saveScheduleInfo(scheduleRegistResDTO); + } + + + +// @Transactional + @Transactional(propagation = Propagation.REQUIRES_NEW) + public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ + logger.info("START DB SAVE!"); + try { + + // 1. Schedule + Schedule schedule = Schedule.builder() + .membershipNo(1) // todo. token에서 정보 가져오는 방식으로 수정 필요 + .scheduleNm(scheduleRegistResDTO.getScheduleNm()) + .startDate(scheduleRegistResDTO.getStartDate()) + .endDate(scheduleRegistResDTO.getEndDate()) + // .radius(radius) + .delYn("N") + .build(); + + scheduleRepository.save(schedule); + logger.info("schedule Save! scheduleNum={}", schedule.getScheduleNum()); + + // 2. Schedule_tag + if (scheduleRegistResDTO.getScheduleTagRegistResDTOList() != null) { + for (TagRegistResDTO tagReq : scheduleRegistResDTO.getScheduleTagRegistResDTOList()) { + Tag tag = tagRepository.findById(tagReq.getTagNum()) + .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagReq.getTagNum())); + + scheduleTagRepository.save( + ScheduleTag.builder() + .id(new ScheduleTagId(tag.getTagNum(), schedule.getScheduleNum())) + .schedule(schedule) + .tag(tag) + .build() + ); + } + } + logger.info("scheduleTag Save! scheduleNum={}", schedule.getScheduleNum()); + + // 3. Plan + Plan_tag + Plan_region + Place + for (int i = 0; i < scheduleRegistResDTO.getPlanRegistResDTOList().size(); i++) { + + PlanRegistResDTO planRes = scheduleRegistResDTO.getPlanRegistResDTOList().get(i); + + Item item = itemRepository.findById(planRes.getItemNum()) + .orElseThrow(() -> new IllegalArgumentException("Item not found: " + planRes.getItemNum())); + + PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() + .membershipNo(1) // todo. token에서 정보 가져오는 방식으로 수정 필요 + .build(); + + // 3-1. Plan + Plan plan = Plan.builder() + .planNum(planRes.getPlanNum()) + .planNm(planRes.getPlanNm()) + .startTime(planRes.getStartTime()) + .endTime(planRes.getEndTime()) + .planLink(planRes.getPlanLink()) + .planDescription(planRes.getPlanDescription()) + .planAddress(planRes.getPlanAddress()) + .schedule(schedule) + .user(user) + .item(item) + .delYn("N") + .build(); + + planRepository.saveAndFlush(plan); + logger.info("Plan save! planNum={}", plan.getPlanNum()); + + + // 3-2. Plan_tag + if (planRes.getPlanTagRegistResDTOList() != null) { + + for (TagRegistResDTO tagRes : planRes.getPlanTagRegistResDTOList()) { + Tag tag = tagRepository.findById(tagRes.getTagNum()) + .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagRes.getTagNum())); + + PlanTag planTag = PlanTag.builder() + .id(new PlanTagId(tag.getTagNum(), plan.getPlanNum())) + .plan(plan) + .tag(tag) + .build(); + + planTagRepository.save(planTag); + } + } + + // 3-3. Plan_region + if (planRes.getRegionNum() != null) { + Region region = regionRepository.findById(planRes.getRegionNum()) + .orElseThrow(() -> new IllegalArgumentException("Region not found: " + planRes.getRegionNum())); + + PlanRegionId planRegionId = new PlanRegionId(plan.getPlanNum(), region.getRegionNum()); + + PlanRegion planRegion = PlanRegion.builder() + .id(planRegionId) + .region(region) + .plan(plan) + .build(); + + planRegionRepository.save(planRegion); + logger.info("PlanRegion save! regionNum={}, planNum={}", region.getRegionNum(), plan.getPlanNum()); + + } + + // 3-4. Place + if (planRes.getRegionNum() != null) { + Region region = regionRepository.findById(planRes.getRegionNum()) + .orElseThrow(() -> new IllegalArgumentException("Region not found: " + planRes.getRegionNum())); + + Place place = Place.builder() + .region(region) + .regionNm(region.getRegionLevel3() != null ? region.getRegionLevel3() : region.getRegionLevel2()) + .address(planRes.getPlanAddress()) + .planLink(planRes.getPlanLink()) + .placeDescription(planRes.getPlanDescription()) + .plan(plan) + .build(); + + placeRepository.save(place); + logger.info("Place save! planNum={}, regionNum={}", plan.getPlanNum(), planRes.getRegionNum()); + + // 3-5. plan-place 동기화 + plan.toBuilder().place(place).build(); + } + + } + logger.info("END DB SAVE!"); + + return schedule.getScheduleNum(); + + } catch (Exception e) { + logger.error(e.getMessage()); + } + + return null; + + } + + + @Transactional + public boolean deleteSchedule(Integer scheduleNum, Integer membershipNo) { + Optional optional = scheduleRepository.findByScheduleNumAndMembershipNo(scheduleNum, membershipNo); + if (optional.isPresent()) { + Schedule schedule = optional.get(); + schedule.markDeleted(); // del_yn=Y로 변경 + return true; // 트랜잭션 커밋 시 자동 UPDATE + } + return false; + } + + @Transactional + public boolean updateSchedule(ScheduleRegistResDTO dto) { + + // 1) Schedule 조회 + Schedule schedule = scheduleRepository.findById(dto.getScheduleNum()) + .orElseThrow(() -> new IllegalArgumentException("Schedule not found: " + dto.getScheduleNum())); + + // 2) Schedule 기본 정보 업데이트 + schedule.updateBasicInfo( + dto.getScheduleNm(), + dto.getStartDate(), + dto.getEndDate() + ); + + // 3) ScheduleTag 전체 삭제 후 재등록 + scheduleTagRepository.deleteBySchedule(schedule); + + if (dto.getScheduleTagRegistResDTOList() != null) { + for (TagRegistResDTO tagReq : dto.getScheduleTagRegistResDTOList()) { + Tag tag = tagRepository.findById(tagReq.getTagNum()) + .orElseThrow(() -> new IllegalArgumentException("Tag not found")); + + scheduleTagRepository.save( + ScheduleTag.builder() + .id(new ScheduleTagId(tag.getTagNum(), schedule.getScheduleNum())) + .schedule(schedule) + .tag(tag) + .build() + ); + } + } + + // 4) 기존 Plan은 soft delete 처리 + List oldPlans = planRepository.findBySchedule(schedule); + for (Plan p : oldPlans) { + p.markDeleted(); + } + + // 5) 새 Plan + PlanTag + PlanRegion + Place 저장 + if (dto.getPlanRegistResDTOList() != null) { + + for (PlanRegistResDTO planRes : dto.getPlanRegistResDTOList()) { + + Item item = itemRepository.findById(planRes.getItemNum()) + .orElseThrow(() -> new IllegalArgumentException("Item not found")); + + PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() + .membershipNo(1) + .build(); + + // 새 Plan 생성 + Plan plan = Plan.builder() + .planNm(planRes.getPlanNm()) + .startTime(planRes.getStartTime()) + .endTime(planRes.getEndTime()) + .planLink(planRes.getPlanLink()) + .planDescription(planRes.getPlanDescription()) + .planAddress(planRes.getPlanAddress()) + .schedule(schedule) + .user(user) + .item(item) + .delYn("N") + .build(); + + planRepository.save(plan); + + // PlanTag 저장 + if (planRes.getPlanTagRegistResDTOList() != null) { + for (TagRegistResDTO tagRes : planRes.getPlanTagRegistResDTOList()) { + Tag tag = tagRepository.findById(tagRes.getTagNum()) + .orElseThrow(() -> new IllegalArgumentException("Tag not found")); + + planTagRepository.save( + new PlanTag(new PlanTagId(tag.getTagNum(), plan.getPlanNum()), plan, tag) + ); + } + } + + // PlanRegion + Place 저장 + if (planRes.getRegionNum() != null) { + Region region = regionRepository.findById(planRes.getRegionNum()) + .orElseThrow(() -> new IllegalArgumentException("Region not found")); + + // PlanRegion + PlanRegionId regionId = new PlanRegionId(plan.getPlanNum(), region.getRegionNum()); + planRegionRepository.save(new PlanRegion(regionId, plan, region)); + + // Place + Place place = Place.builder() + .region(region) + .regionNm(region.getRegionLevel3() != null ? region.getRegionLevel3() : region.getRegionLevel2()) + .address(planRes.getPlanAddress()) + .planLink(planRes.getPlanLink()) + .placeDescription(planRes.getPlanDescription()) + .plan(plan) + .build(); + + placeRepository.save(place); + } + } + } + + return true; + } + + + // 후보지역 수에 따라 각 지역의 후보장소 수를 리턴 + // 후보장소 수만큼 네이버 블로그 API를 호출해야 하기 때문에 제한 필요 + private int calLimitPlace(int regionCount) { + int perRegionLimit; + if (regionCount == 1) { + perRegionLimit = 5; + } else if (regionCount == 2) { + perRegionLimit = 3; + } else { + perRegionLimit = 2; + } + return perRegionLimit; + } + + private String resolveRegionName(RegionDetailVO region) { + if (region.getRegionLevel3() != null && !region.getRegionLevel3().isEmpty()) { + return region.getRegionLevel3(); + } else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty()) { + return region.getRegionLevel2(); + } else { + return region.getRegionLevel1(); + } + } + +// private String firstRegionName(List regions) { +// if (regions == null || regions.isEmpty()) return null; +// // RegionRegistReqDTO에 regionName 같은 필드가 있다면 그걸 반환 +// // 예시로 cityName+district 조합 등을 사용 +// return Optional.ofNullable(regions.get(0).getRegionName()).orElse(null); +// } } diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 13b02e5..3d64951 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -1,19 +1,35 @@ package com.barogagi.schedule.controller; -//import com.barogagi.member.join.vo.JoinVO; +import com.barogagi.member.join.vo.JoinVO; +import com.barogagi.member.join.vo.UserIdCheckVO; +import com.barogagi.member.login.controller.LoginController; +import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.query.service.PlanQueryService; +import com.barogagi.plan.query.vo.PlanDetailVO; +import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.region.dto.RegionSearchResDTO; import com.barogagi.response.ApiResponse; +import com.barogagi.schedule.command.service.ScheduleCommandService; import com.barogagi.schedule.dto.ScheduleDetailResDTO; +import com.barogagi.schedule.dto.ScheduleListResDTO; +import com.barogagi.schedule.dto.ScheduleRegistReqDTO; +import com.barogagi.schedule.dto.ScheduleRegistResDTO; import com.barogagi.schedule.query.service.ScheduleQueryService; +import com.barogagi.schedule.query.vo.ScheduleDetailVO; import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.List; + @Tag(name = "일정", description = "일정 관련 API") @RestController @RequestMapping("/schedule") @@ -23,6 +39,7 @@ public class ScheduleController { private final InputValidate inputValidate; private final ScheduleQueryService scheduleQueryService; + private final ScheduleCommandService scheduleCommandService; private final PlanQueryService planQueryService; private final String API_SECRET_KEY; @@ -30,67 +47,363 @@ public class ScheduleController { public ScheduleController(Environment environment, InputValidate inputValidate, ScheduleQueryService scheduleQueryService, + ScheduleCommandService scheduleCommandService, PlanQueryService planQueryService) { this.API_SECRET_KEY = environment.getProperty("api.secret-key"); this.inputValidate = inputValidate; this.scheduleQueryService = scheduleQueryService; + this.scheduleCommandService = scheduleCommandService; this.planQueryService = planQueryService; } + @Operation(summary = "내 일정 목록 조회 기능", description = "일정 목록을 조회하는 기능입니다.") + @GetMapping("/list") + public ApiResponse getScheduleList() { + + logger.info("CALL /schedule/list"); + + List result; + try { + // TODO. token으로 사용자 확인 후, 해당 사용자의 일정만 조회하도록 수정해야 함 + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ + result = scheduleQueryService.getScheduleList(1); + + } catch (Exception e) { + return ApiResponse.error("404", "일정 목록 조회 실패"); + } + + + return ApiResponse.success(result, "일정 목록 조회 성공"); + } + @Operation(summary = "일정 상세 조회 기능", description = "일정을 상세 조회하는 기능입니다.") @GetMapping("/detail") public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 번호", example = "1") @RequestParam Integer scheduleNum) { + logger.info("CALL /schedule/detail"); - logger.info("[input] SchedulNm={}", scheduleNum); + logger.info("[input] scheduleNum={}", scheduleNum); + + ScheduleDetailResDTO result; + try { + // TODO. 해당 사용자의 일정이 맞는지도 체크해야 함 + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ + result = scheduleQueryService.getScheduleDetail(scheduleNum); + + } catch (Exception e) { + return ApiResponse.error("404", "일정 조회 실패"); + } + + + return ApiResponse.success(result, "일정 조회 성공"); + } + + + @Operation(summary = "일정 생성 기능", + description = "일정을 생성하는 기능입니다.
" + + "- 사용자가 직접 일정을 생성하는 경우는 2가지가 존재합니다.
" + + "     CASE 1. 카카오 장소 검색 API를 사용해서 사용자가 가고 싶은 장소를 선택하는 경우, 카카오 장소 검색 API에서 검색한 placeName, placeUrl, addressName을 보내주세요.
" + + "     CASE 2. 사용자가 세부일정을 직접 텍스트로 입력하는 경우(ex, 친구집 방문), 세부일정명을 planNm 필드에 담아 보내주세요.
" + + "     주의 1) 반드시 isUserAdded=\"Y\"로 전달해 주세요.
" + + "     주의 2) 사용자가 직접 일정을 생성하는 경우 planTagRegistReqDTOList 값을 전달할 필요는 없습니다.
" + + "- 생성된 일정은 '일정 등록'과정을 거쳐야 DB에 저장됩니다.
" + + "- 사용자가 이 API로 생성된 일정을 확인한 후 '일정 생성하기' 버튼을 누르면 '일정 등록' API를 호출해 주세요.") + @PostMapping("/create") + public ApiResponse createSchedule( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "일정 등록 요청", + required = true, + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "일정 등록 요청 예시", + value = "{\n" + + " \"scheduleNm\": \"서울 카페투어\",\n" + + " \"startDate\": \"2025-12-01\",\n" + + " \"endDate\": \"2025-12-01\",\n" + + " \"comment\": \"분위기 좋은 카페 추천해주세요\",\n" + + " \"scheduleTagRegistReqDTOList\": [\n" + + " { \"tagNm\": \"핫플\", \"tagNum\": 5 },\n" + + " { \"tagNm\": \"활동적인\", \"tagNum\": 8 }\n" + + " ],\n" + + " \"planRegistReqDTOList\": [\n" + + " {\n" + + " \"startTime\": \"08:00\",\n" + + " \"endTime\": \"09:00\",\n" + + " \"itemNum\": 10,\n" + + " \"categoryNum\": 2,\n" + + " \"isUserAdded\": \"N\",\n" + + " \"regionRegistReqDTOList\": [\n" + + " { \"regionNum\": 1 }\n" + + " ],\n" + + " \"planTagRegistReqDTOList\": [\n" + + " { \"tagNm\": \"디저트맛집\", \"tagNum\": 14 },\n" + + " { \"tagNm\": \"인스타핫플\", \"tagNum\": 15 }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"startTime\": \"14:00\",\n" + + " \"endTime\": \"15:00\",\n" + + " \"itemNum\": 2,\n" + + " \"categoryNum\": 1,\n" + + " \"isUserAdded\": \"Y\",\n" + + " \"userAddedPlaceDTO\": {\n" + + " \"placeName\": \"카카오프렌즈 코엑스점\",\n" + + " \"placeUrl\": \"http://place.map.kakao.com/26338954\",\n" + + " \"addressName\": \"서울 강남구 삼성동 159\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"startTime\": \"15:30\",\n" + + " \"endTime\": \"19:00\",\n" + + " \"itemNum\": 15,\n" + + " \"categoryNum\": 4,\n" + + " \"isUserAdded\": \"Y\",\n" + + " \"planNm\": \"친구집 방문\",\n" + + " \"regionRegistReqDTOList\": [\n" + + " { \"regionNum\": 1 }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}" + + ) + ) + ) + @RequestBody ScheduleRegistReqDTO scheduleRegistReqDTO + ) { + + logger.info("CALL /schedule/create"); + logger.info("[input] scheduleRegistReqDTO={}", scheduleRegistReqDTO); + + ScheduleRegistResDTO result; + try { + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ + result = scheduleCommandService.createSchedule(scheduleRegistReqDTO); + + } catch (Exception e) { + return ApiResponse.error("404", "일정 생성 실패"); + } + + + return ApiResponse.success(result, "일정 생성 성공"); + } - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; + @Operation(summary = "일정 저장 기능", + description = "일정을 DB에 저장하는 기능입니다.
" + + "- '일정 생성하기' 버튼을 눌렀을 때 호출되는 API입니다.
" + + "- '일정 생성' API로 받은 응답 DTO를 그대로 보내주세요.") + @PostMapping("/save") + public ApiResponse saveSchedule( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "일정 등록 요청", + required = true, + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "일정 등록 요청 예시", + value = "{\n" + + " \"scheduleNum\": null,\n" + + " \"scheduleNm\": \"서울 데이트 코스\",\n" + + " \"startDate\": \"2025-07-01\",\n" + + " \"endDate\": \"2025-07-01\",\n" + + " \"planRegistResDTOList\": [\n" + + " {\n" + + " \"startTime\": \"08:30\",\n" + + " \"endTime\": \"09:00\",\n" + + " \"itemNum\": 10,\n" + + " \"itemNm\": \"프랜차이즈카페\",\n" + + " \"categoryNum\": 2,\n" + + " \"categoryNm\": \"카페\",\n" + + " \"planNm\": \"제비꽃다방\",\n" + + " \"planLink\": \"http://place.map.kakao.com/24944966\",\n" + + " \"planDescription\": \"분위기 좋은 한옥 카페 '더숲 초소책방'은 서울 종로구에 위치해 있으며, 숲속의 아늑함을 느낄 수 있는 넓은 야외 공간과 아름다운 서울 풍경을 감상할 수 있는 2층 테라스가 특징입니다.\",\n" + + " \"planAddress\": \"서울 종로구 창의문로 146\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 14, \"tagNm\": \"디저트맛집\" },\n" + + " { \"tagNum\": 15, \"tagNm\": \"인스타핫플\" }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"startTime\": \"14:00\",\n" + + " \"endTime\": \"15:00\",\n" + + " \"itemNum\": 2,\n" + + " \"itemNm\": \"한식\",\n" + + " \"categoryNum\": 1,\n" + + " \"categoryNm\": \"식사\",\n" + + " \"planNm\": \"식사\",\n" + + " \"planLink\": \"http://place.map.kakao.com/1581311090\",\n" + + " \"planDescription\": \"분위기 좋은 카페로 뷰가 좋은 곳입니다.\",\n" + + " \"planAddress\": \"서울 중구 무교로 17\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 17, \"tagNm\": \"조용한\" }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"startTime\": \"15:30\",\n" + + " \"endTime\": \"19:00\",\n" + + " \"itemNum\": 15,\n" + + " \"itemNm\": \"놀이공원\",\n" + + " \"categoryNum\": 4,\n" + + " \"categoryNm\": \"놀거리\",\n" + + " \"planNm\": \"구룡관 혜화본점\",\n" + + " \"planLink\": \"http://place.map.kakao.com/40669117\",\n" + + " \"planDescription\": \"혜화에서 분위기 좋고 저렴한 중식 술집으로는 구룡관 혜화본점이 추천됩니다.\",\n" + + " \"planAddress\": \"서울 종로구 창경궁로 258-5\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 4, \"tagNm\": \"테마파크\" }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}" + ) + ) + ) + @RequestBody ScheduleRegistResDTO scheduleRegistResDTO + ) { + + logger.info("CALL /schedule/save"); + logger.info("[input] scheduleRegistResDTO={}", scheduleRegistResDTO); + + int result; try { + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ + result = scheduleCommandService.saveSchedule(scheduleRegistResDTO); + + } catch (Exception e) { + return ApiResponse.error("404", "일정 저장 실패"); + } + + + return ApiResponse.success(result, "일정 저장 성공"); + } + + @Operation(summary = "일정 수정 기능", + description = "DB에 저장되어 있는 일정을 수정하는 기능입니다.
" + + "- 전체 일정 내 세부 일정을 수정/삭제하는 경우에도 이 API를 호출해주세요.
" + + "- '일정 번호'가 반드시 필요합니다.
" + + "- '일정 조회' API로 받은 응답 DTO를 수정하여 보내주세요.") + @PutMapping("/") + public ApiResponse updateSchedule( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "일정 등록 요청", + required = true, + content = @Content( + mediaType = "application/json", + examples = @ExampleObject( + name = "일정 수정 요청 예시", + value = "{\n" + + " \"scheduleNum\": 1,\n" + + " \"scheduleNm\": \"서울 데이트 코스2\",\n" + + " \"startDate\": \"2025-07-01\",\n" + + " \"endDate\": \"2025-07-01\",\n" + + " \"planRegistResDTOList\": [\n" + + " {\n" + + " \"startTime\": \"08:30\",\n" + + " \"endTime\": \"09:00\",\n" + + " \"itemNum\": 10,\n" + + " \"itemNm\": \"프랜차이즈카페\",\n" + + " \"categoryNum\": 2,\n" + + " \"categoryNm\": \"카페\",\n" + + " \"planNm\": \"제비꽃다방\",\n" + + " \"planLink\": \"http://place.map.kakao.com/24944966\",\n" + + " \"planDescription\": \"분위기 좋은 한옥 카페 '더숲 초소책방'은 서울 종로구에 위치해 있으며, 숲속의 아늑함을 느낄 수 있는 넓은 야외 공간과 아름다운 서울 풍경을 감상할 수 있는 2층 테라스가 특징입니다.\",\n" + + " \"planAddress\": \"서울 종로구 창의문로 146\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 14, \"tagNm\": \"디저트맛집\" },\n" + + " { \"tagNum\": 15, \"tagNm\": \"인스타핫플\" }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"startTime\": \"14:00\",\n" + + " \"endTime\": \"15:00\",\n" + + " \"itemNum\": 2,\n" + + " \"itemNm\": \"한식\",\n" + + " \"categoryNum\": 1,\n" + + " \"categoryNm\": \"식사\",\n" + + " \"planNm\": \"식사\",\n" + + " \"planLink\": \"http://place.map.kakao.com/1581311090\",\n" + + " \"planDescription\": \"분위기 좋은 카페로 뷰가 좋은 곳입니다.\",\n" + + " \"planAddress\": \"서울 중구 무교로 17\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 17, \"tagNm\": \"조용한\" }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"startTime\": \"15:30\",\n" + + " \"endTime\": \"19:00\",\n" + + " \"itemNum\": 15,\n" + + " \"itemNm\": \"놀이공원\",\n" + + " \"categoryNum\": 4,\n" + + " \"categoryNm\": \"놀거리\",\n" + + " \"planNm\": \"구룡관 혜화본점\",\n" + + " \"planLink\": \"http://place.map.kakao.com/40669117\",\n" + + " \"planDescription\": \"혜화에서 분위기 좋고 저렴한 중식 술집으로는 구룡관 혜화본점이 추천됩니다.\",\n" + + " \"planAddress\": \"서울 종로구 창경궁로 258-5\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 4, \"tagNm\": \"테마파크\" }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}" + ) + ) + ) + @RequestBody ScheduleRegistResDTO scheduleRegistResDTO + ) { + logger.info("CALL /schedule/update"); + logger.info("[input] scheduleRegistResDTO={}", scheduleRegistResDTO); + + boolean result; + try { //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ - if (true) { - - // TODO. 해당 사용자의 일정이 맞는지도 체크해야 함 - if (inputValidate.isInvalidInteger(scheduleNum)) { - resultCode = "101"; - message = "조회할 일정을 선택해주세요."; - } else { - - ScheduleDetailResDTO result = scheduleQueryService.getScheduleDetail(scheduleNum); - logger.info("result={}", result.toString()); - - if (result == null) { - resultCode = "300"; - message = "조회할 일정이 존재하지 않습니다."; // TODO. 에러 메시지 정의하기 - - } else { - resultCode = "200"; - message = "일정 상세 조회 성공"; - apiResponse.setData(result); - logger.info("#$# result={}", result.toString()); - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - logger.info("#$# 11 resultCode={}", resultCode); + result = scheduleCommandService.updateSchedule(scheduleRegistResDTO); + } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - logger.info("#$# 22 resultCode={}", resultCode); - logger.info("#$# 22 apiResponse.getResultCode()={}", apiResponse.getResultCode()); + return ApiResponse.error("404", "일정 저장 실패"); } - return apiResponse; + + + return ApiResponse.success(result, "일정 저장 성공"); + } + + @Operation(summary = "일정 전체 삭제 기능", + description = "일정 전체를 DB에서 삭제하는 기능입니다.") + @DeleteMapping("/") + public ApiResponse deleteSchedule(@Parameter(description = "삭제할 일정 번호", example = "1") + @RequestParam Integer scheduleNum) { + + logger.info("CALL /schedule/delete"); + logger.info("[input] scheduleNum={}", scheduleNum); + + boolean result; + try { + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ + // TODO. membershipNo를 토큰으로부터 받아와야 함 + result = scheduleCommandService.deleteSchedule(scheduleNum, 1); + + if (!result) return ApiResponse.error("404", "일정 삭제 실패"); + + } catch (Exception e) { + return ApiResponse.error("404", "일정 삭제 실패"); + } + + + return ApiResponse.success(result, "일정 삭제 성공"); } } diff --git a/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java b/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java new file mode 100644 index 0000000..dbb3384 --- /dev/null +++ b/src/main/java/com/barogagi/schedule/dto/KakaoPlaceReqDTO.java @@ -0,0 +1,11 @@ +//package com.barogagi.schedule.dto; +// +//import com.fasterxml.jackson.annotation.JsonProperty; +// +//public class KakaoPlaceReqDTO { +// @JsonProperty("address_name") +// String addressName; // 상세주소 +// +// @JsonProperty("place_name") +// String placeName; // 장소명 +//} diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleListResDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleListResDTO.java new file mode 100644 index 0000000..0f2e736 --- /dev/null +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleListResDTO.java @@ -0,0 +1,22 @@ +package com.barogagi.schedule.dto; + +import com.barogagi.tag.dto.TagRegistResDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +@Builder(toBuilder = true) +@Schema(description = "일정 목록 조회 DTO") +public class ScheduleListResDTO { + private int scheduleNum; // 일정 번호 (PK) + private String scheduleNm; // 일정명 + private String startDate; // 시작 날짜 + private String endDate; // 종료 날짜 + + List scheduleTagRegistResDTOList; +} diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java index fdd01f2..5a8ba4c 100644 --- a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistReqDTO.java @@ -3,6 +3,8 @@ import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.query.vo.PlanDetailVO; import com.barogagi.region.dto.RegionRegistReqDTO; +import com.barogagi.tag.dto.TagRegistReqDTO; +import com.barogagi.tag.dto.TagRegistResDTO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Getter; import lombok.ToString; @@ -16,10 +18,10 @@ public class ScheduleRegistReqDTO { private String scheduleNm; // 일정명 private String startDate; // 시작 날짜 private String endDate; // 종료 날짜 - private int radius; // 반경 + private String comment; // 추가 고려사항 - // 지역 리스트 - private List regionRegistReqDTOList; + // 일정 태그 목록 (스케쥴 태그) + public List scheduleTagRegistReqDTOList; // 계획 리스트 private List planRegistReqDTOList; diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java new file mode 100644 index 0000000..8be5e98 --- /dev/null +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleRegistResDTO.java @@ -0,0 +1,30 @@ +package com.barogagi.schedule.dto; + +import com.barogagi.plan.dto.PlanRegistReqDTO; +import com.barogagi.plan.dto.PlanRegistResDTO; +import com.barogagi.tag.dto.TagRegistReqDTO; +import com.barogagi.tag.dto.TagRegistResDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +@Builder(toBuilder = true) +@Schema(description = "일정 등록 응답 DTO") +public class ScheduleRegistResDTO { + private Integer scheduleNum; // 일정 번호 + private String scheduleNm; // 일정명 + private String startDate; // 시작 날짜 + private String endDate; // 종료 날짜 + + // 일정 태그 목록 (스케쥴 태그) + public List scheduleTagRegistResDTOList; + + // 계획 리스트 + private List planRegistResDTOList; + +} diff --git a/src/main/java/com/barogagi/schedule/query/mapper/ScheduleMapper.java b/src/main/java/com/barogagi/schedule/query/mapper/ScheduleMapper.java index 034bf6a..16862df 100644 --- a/src/main/java/com/barogagi/schedule/query/mapper/ScheduleMapper.java +++ b/src/main/java/com/barogagi/schedule/query/mapper/ScheduleMapper.java @@ -1,9 +1,15 @@ package com.barogagi.schedule.query.mapper; +import com.barogagi.schedule.dto.ScheduleListResDTO; import com.barogagi.schedule.query.vo.ScheduleDetailVO; +import com.barogagi.schedule.query.vo.ScheduleListVO; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + @Mapper public interface ScheduleMapper { ScheduleDetailVO selectScheduleDetail(int scheduleNum); + + List selectScheduleList(int membershipNo); } diff --git a/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java b/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java index 976e0b7..ade46bd 100644 --- a/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java +++ b/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java @@ -3,8 +3,10 @@ import com.barogagi.plan.query.service.PlanQueryService; import com.barogagi.plan.query.vo.PlanDetailVO; import com.barogagi.schedule.dto.ScheduleDetailResDTO; +import com.barogagi.schedule.dto.ScheduleListResDTO; import com.barogagi.schedule.query.mapper.ScheduleMapper; import com.barogagi.schedule.query.vo.ScheduleDetailVO; +import com.barogagi.schedule.query.vo.ScheduleListVO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +30,23 @@ public ScheduleQueryService (ScheduleMapper scheduleMapper, this.planQueryService = planQueryService; } + public List getScheduleList(int membershipNo) { + List scheduleListVOList = scheduleMapper.selectScheduleList(membershipNo); + + // VO -> DTO 변환 + List scheduleListResDTOList = scheduleListVOList.stream().map(scheduleListVO -> + ScheduleListResDTO.builder() + .scheduleNum(scheduleListVO.getScheduleNum()) + .scheduleNm(scheduleListVO.getScheduleNm()) + .startDate(scheduleListVO.getStartDate()) + .endDate(scheduleListVO.getEndDate()) + .scheduleTagRegistResDTOList(scheduleListVO.getScheduleTagRegistResDTOList()) + .build() + ).toList(); + + return scheduleListResDTOList; + } + public ScheduleDetailResDTO getScheduleDetail(int scheduleNum) throws Exception{ // 일정 정보 조회 ScheduleDetailVO scheduleDetailVO = scheduleMapper.selectScheduleDetail(scheduleNum); diff --git a/src/main/java/com/barogagi/schedule/query/vo/ScheduleListVO.java b/src/main/java/com/barogagi/schedule/query/vo/ScheduleListVO.java new file mode 100644 index 0000000..2899893 --- /dev/null +++ b/src/main/java/com/barogagi/schedule/query/vo/ScheduleListVO.java @@ -0,0 +1,18 @@ +package com.barogagi.schedule.query.vo; + +import com.barogagi.tag.dto.TagRegistResDTO; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +public class ScheduleListVO { + public int scheduleNum; // 일정 번호 (PK) + public String scheduleNm; // 일정명 + public String startDate; // 시작 날짜 + public String endDate; // 종료 날짜 + + List scheduleTagRegistResDTOList; +} diff --git a/src/main/java/com/barogagi/tag/command/entity/PlanTag.java b/src/main/java/com/barogagi/tag/command/entity/PlanTag.java new file mode 100644 index 0000000..61c9e1a --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/PlanTag.java @@ -0,0 +1,28 @@ +package com.barogagi.tag.command.entity; + +import com.barogagi.plan.command.entity.Plan; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Table(name = "PLAN_TAG") +public class PlanTag { + + @EmbeddedId + private PlanTagId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("planNum") + @JoinColumn(name = "PLAN_NUM") + private Plan plan; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("tagNum") + @JoinColumn(name = "TAG_NUM") + private Tag tag; +} + diff --git a/src/main/java/com/barogagi/tag/command/entity/PlanTagId.java b/src/main/java/com/barogagi/tag/command/entity/PlanTagId.java new file mode 100644 index 0000000..1f5fdf4 --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/PlanTagId.java @@ -0,0 +1,19 @@ +package com.barogagi.tag.command.entity; + +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@Embeddable +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PlanTagId implements Serializable { + private Integer tagNum; + private Integer planNum; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java new file mode 100644 index 0000000..eb2c120 --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java @@ -0,0 +1,29 @@ +package com.barogagi.tag.command.entity; + +import com.barogagi.schedule.command.entity.Schedule; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Table(name = "SCHEDULE_TAG") +public class ScheduleTag { + + @EmbeddedId + private ScheduleTagId id; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("scheduleNum") + @JoinColumn(name = "SCHEDULE_NUM") + private Schedule schedule; + + @ManyToOne(fetch = FetchType.LAZY) + @MapsId("tagNum") + @JoinColumn(name = "TAG_NUM") + private Tag tag; +} + + diff --git a/src/main/java/com/barogagi/tag/command/entity/ScheduleTagId.java b/src/main/java/com/barogagi/tag/command/entity/ScheduleTagId.java new file mode 100644 index 0000000..94efa0b --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/ScheduleTagId.java @@ -0,0 +1,19 @@ +package com.barogagi.tag.command.entity; + +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +@Embeddable +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleTagId implements Serializable { + private Integer tagNum; + private Integer scheduleNum; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/command/entity/Tag.java b/src/main/java/com/barogagi/tag/command/entity/Tag.java new file mode 100644 index 0000000..72ecd43 --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/entity/Tag.java @@ -0,0 +1,33 @@ +package com.barogagi.tag.command.entity; + + +import com.barogagi.plan.command.entity.Category; +import com.barogagi.tag.enums.TagType; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) // 무분별한 객체 생성 방지 +@AllArgsConstructor +@Builder(toBuilder = true) +@Table(name = "TAG") +@ToString(exclude = "category") +public class Tag { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "TAG_NUM") + private Integer tagNum; + + @Column(name = "TAG_NM", nullable = false, length = 100) + private String tagNm; + + @Enumerated(EnumType.STRING) + @Column(name = "TAG_TYPE", nullable = false, length = 1) + private TagType tagType; // ENUM('P', 'S') + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CATEGORY_NUM") + private Category category; +} diff --git a/src/main/java/com/barogagi/tag/command/repository/ScheduleTagRepository.java b/src/main/java/com/barogagi/tag/command/repository/ScheduleTagRepository.java new file mode 100644 index 0000000..64f35bc --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/repository/ScheduleTagRepository.java @@ -0,0 +1,14 @@ +package com.barogagi.tag.command.repository; + +import com.barogagi.schedule.command.entity.Schedule; +import com.barogagi.tag.command.entity.ScheduleTag; +import com.barogagi.tag.command.entity.ScheduleTagId; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ScheduleTagRepository extends JpaRepository { + + void deleteBySchedule(Schedule schedule); + +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/command/repository/TagRepository.java b/src/main/java/com/barogagi/tag/command/repository/TagRepository.java new file mode 100644 index 0000000..c931a22 --- /dev/null +++ b/src/main/java/com/barogagi/tag/command/repository/TagRepository.java @@ -0,0 +1,12 @@ +package com.barogagi.tag.command.repository; +import com.barogagi.tag.command.entity.Tag; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface TagRepository extends JpaRepository { +// Optional findById(Integer tagNum); +} + diff --git a/src/main/java/com/barogagi/tag/controller/TagController.java b/src/main/java/com/barogagi/tag/controller/TagController.java new file mode 100644 index 0000000..bac8f4e --- /dev/null +++ b/src/main/java/com/barogagi/tag/controller/TagController.java @@ -0,0 +1,54 @@ +package com.barogagi.tag.controller; + +import com.barogagi.response.ApiResponse; +import com.barogagi.schedule.dto.ScheduleRegistReqDTO; +import com.barogagi.tag.dto.TagSearchReqDTO; +import com.barogagi.tag.dto.TagSearchResDTO; +import com.barogagi.tag.query.service.TagQueryService; +import com.barogagi.util.InputValidate; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "태그", description = "태그 관련 API") +@RestController +@RequestMapping("/tag") +public class TagController { + private static final Logger logger = LoggerFactory.getLogger(TagController.class); + + private final InputValidate inputValidate; + + private final String API_SECRET_KEY; + + private final TagQueryService tagQueryService; + + public TagController(Environment environment, InputValidate inputValidate, + TagQueryService tagQueryService) { + this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + this.inputValidate = inputValidate; + this.tagQueryService = tagQueryService; + } + + @Operation( + summary = "태그 목록 검색", + description = "태그 목록을 검색하는 기능입니다.
" + + "- 여행 스타일 태그(S): categoryNum을 null로 전달하세요.
" + + "- 상세 일정 태그(P): 해당 일정의 카테고리 번호(categoryNum)를 전달하세요.
" + + "검색 결과는 최대 10개의 태그를 반환합니다." + ) + @PostMapping("/searchList") + public ApiResponse searchList(@RequestBody TagSearchReqDTO tagSearchReqDTO) { + + logger.info("CALL /tag/searchList"); + + List result = tagQueryService.searchList(tagSearchReqDTO); + return ApiResponse.success(result, "태그 목록 검색 성공"); + + } + +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/dto/TagRegistResDTO.java b/src/main/java/com/barogagi/tag/dto/TagRegistResDTO.java new file mode 100644 index 0000000..2671ef6 --- /dev/null +++ b/src/main/java/com/barogagi/tag/dto/TagRegistResDTO.java @@ -0,0 +1,18 @@ +package com.barogagi.tag.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@Builder(toBuilder = true) +@Schema(description = "태그 정보 리스트 DTO") +public class TagRegistResDTO { + @Schema(description = "태그 번호", example = "1") + public int tagNum; + + @Schema(description = "태그 이름", example = "디저트") + public String tagNm; +} diff --git a/src/main/java/com/barogagi/tag/dto/TagSearchReqDTO.java b/src/main/java/com/barogagi/tag/dto/TagSearchReqDTO.java new file mode 100644 index 0000000..4dc33bb --- /dev/null +++ b/src/main/java/com/barogagi/tag/dto/TagSearchReqDTO.java @@ -0,0 +1,14 @@ +package com.barogagi.tag.dto; + +import com.barogagi.tag.enums.TagType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.ToString; + +public class TagSearchReqDTO { + @Schema(description = "태그 타입 (S는 스타일 태그, P는 세부 일정 태그)", example = "P") + public TagType tagType; + + @Schema(description = "카테고리 번호 (스타일 태그인 경우 null)", example = "1") + public Integer categoryNum; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/dto/TagSearchResDTO.java b/src/main/java/com/barogagi/tag/dto/TagSearchResDTO.java new file mode 100644 index 0000000..7c319f9 --- /dev/null +++ b/src/main/java/com/barogagi/tag/dto/TagSearchResDTO.java @@ -0,0 +1,16 @@ +package com.barogagi.tag.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@Schema(description = "태그 검색 결과 DTO") +public class TagSearchResDTO { + @Schema(description = "태그 번호", example = "1") + public int tagNum; + + @Schema(description = "태그 이름", example = "디저트") + public String tagNm; +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/enums/TagType.java b/src/main/java/com/barogagi/tag/enums/TagType.java new file mode 100644 index 0000000..d4659a6 --- /dev/null +++ b/src/main/java/com/barogagi/tag/enums/TagType.java @@ -0,0 +1,31 @@ +package com.barogagi.tag.enums; + +public enum TagType { + S("일정별 태그"), + P("계획별 태그"); + + private final String description; + + TagType(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + // DB 저장용 코드 반환 + public String getCode() { + return this.name(); + } + + // 코드로부터 Enum 찾기 + public static TagType fromCode(String code) { + for (TagType type : TagType.values()) { + if (type.name().equalsIgnoreCase(code)) { + return type; + } + } + throw new IllegalArgumentException("Unknown TagType code: " + code); + } +} diff --git a/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java b/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java index 42005f8..00a0c62 100644 --- a/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java +++ b/src/main/java/com/barogagi/tag/query/mapper/TagMapper.java @@ -1,5 +1,7 @@ package com.barogagi.tag.query.mapper; +import com.barogagi.tag.dto.TagSearchReqDTO; +import com.barogagi.tag.dto.TagSearchResDTO; import com.barogagi.tag.query.vo.TagDetailVO; import org.apache.ibatis.annotations.Mapper; @@ -10,4 +12,10 @@ public interface TagMapper { // 계획 상세 조회 - 태그 상세 조회 List selectTagByPlanNum (int planNum); + + // 일정 생성 - 태그 번호로 태그명 조회 + TagDetailVO selectTagByTagNum (int tagNum); + + // 태그 리스트 조회 + List selectTagByTagTypeAndCategoryNum(TagSearchReqDTO tagSearchReqDTO); } diff --git a/src/main/java/com/barogagi/tag/query/service/TagQueryService.java b/src/main/java/com/barogagi/tag/query/service/TagQueryService.java new file mode 100644 index 0000000..bf1e3ee --- /dev/null +++ b/src/main/java/com/barogagi/tag/query/service/TagQueryService.java @@ -0,0 +1,44 @@ +package com.barogagi.tag.query.service; + +import com.barogagi.schedule.command.service.ScheduleCommandService; +import com.barogagi.schedule.dto.ScheduleRegistReqDTO; +import com.barogagi.tag.dto.TagSearchReqDTO; +import com.barogagi.tag.dto.TagSearchResDTO; +import com.barogagi.tag.query.mapper.TagMapper; +import com.barogagi.tag.query.vo.TagDetailVO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.stream.Collectors; + +import java.util.List; + +@Service +public class TagQueryService { + private static final Logger logger = LoggerFactory.getLogger(TagQueryService.class); + + private final TagMapper tagMapper; + + @Autowired + public TagQueryService (TagMapper tagMapper) { + this.tagMapper = tagMapper; + } + + // 계획 번호로 연결된 태그 상세 리스트 조회 + public List findTagByPlanNum(int planNum) { + return tagMapper.selectTagByPlanNum(planNum); + } + + // 태그 번호 리스트로 태그명 리스트 조회 + public List findTagNmByTagNum(List tagNums) { + return tagNums.stream() + .map(tagMapper::selectTagByTagNum) + .map(TagDetailVO::getTagNm) + .collect(Collectors.toList()); + } + + public List searchList(TagSearchReqDTO tagSearchReqDTO) { + return tagMapper.selectTagByTagTypeAndCategoryNum(tagSearchReqDTO); + } +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/tag/query/vo/TagDetailVO.java b/src/main/java/com/barogagi/tag/query/vo/TagDetailVO.java index 4748a9b..382fd4c 100644 --- a/src/main/java/com/barogagi/tag/query/vo/TagDetailVO.java +++ b/src/main/java/com/barogagi/tag/query/vo/TagDetailVO.java @@ -1,5 +1,6 @@ package com.barogagi.tag.query.vo; +import com.barogagi.tag.enums.TagType; import lombok.Getter; import lombok.ToString; @@ -8,4 +9,6 @@ public class TagDetailVO { private int tagNum; // 태그 번호 private String tagNm; // 태그명 + private TagType tagType; // 태그 타입 + private int categoryNum; // 카테고리 번호 (일정(스타일) 태그는 null, 계획(plan) 태그는 카테고리 번호) } diff --git a/src/main/java/com/barogagi/tag/query/vo/TagSimpleVO.java b/src/main/java/com/barogagi/tag/query/vo/TagSimpleVO.java new file mode 100644 index 0000000..6ce47ea --- /dev/null +++ b/src/main/java/com/barogagi/tag/query/vo/TagSimpleVO.java @@ -0,0 +1,11 @@ +package com.barogagi.tag.query.vo; + +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class TagSimpleVO { + private int tagNum; // 태그 번호 + private String tagNm; // 태그명 +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/util/HtmlUtils.java b/src/main/java/com/barogagi/util/HtmlUtils.java new file mode 100644 index 0000000..2f6d1da --- /dev/null +++ b/src/main/java/com/barogagi/util/HtmlUtils.java @@ -0,0 +1,37 @@ +package com.barogagi.util; + +import java.util.regex.Pattern; + +public class HtmlUtils { + + private static final Pattern HTML_TAG = Pattern.compile("<[^>]*>"); + + private HtmlUtils() { + // util 클래스는 인스턴스 생성 방지 + } + + public static String stripHtml(String s) { + if (s == null || s.isBlank()) return ""; + // 1) 태그 제거 + String t = HTML_TAG.matcher(s).replaceAll(" "); + // 2) 엔티티 정리 + t = t.replace(" ", " "); + // 3) 공백 정리 + return t.replaceAll("\\s+", " ").trim(); + } + + + /** ```json ... ``` 형태 코드펜스/공백 제거 */ + public static String stripCodeFence(String s) { + if (s == null) return null; + // 앞쪽 ``` 또는 ```json, 뒤쪽 ``` 제거 + String t = s.trim(); + if (t.startsWith("```")) { + t = t.replaceFirst("^```(?:json)?\\s*", ""); + } + if (t.endsWith("```")) { + t = t.replaceFirst("```\\s*$", ""); + } + return t.trim(); + } +} diff --git a/src/main/resources/mapper/CategoryMapper.xml b/src/main/resources/mapper/CategoryMapper.xml new file mode 100644 index 0000000..7e71e6e --- /dev/null +++ b/src/main/resources/mapper/CategoryMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/src/main/resources/mapper/ItemMapper.xml b/src/main/resources/mapper/ItemMapper.xml index 5307fd8..88746f5 100644 --- a/src/main/resources/mapper/ItemMapper.xml +++ b/src/main/resources/mapper/ItemMapper.xml @@ -6,5 +6,12 @@ - + diff --git a/src/main/resources/mapper/PlanMapper.xml b/src/main/resources/mapper/PlanMapper.xml index cb40c8c..82854f5 100644 --- a/src/main/resources/mapper/PlanMapper.xml +++ b/src/main/resources/mapper/PlanMapper.xml @@ -46,7 +46,8 @@ FROM PLAN a JOIN ITEM b ON a.ITEM_NUM = b.ITEM_NUM JOIN CATEGORY c ON b.CATEGORY_NUM = c.CATEGORY_NUM - WHERE a.SCHEDULE_NUM = #{scheduleNum}; + WHERE a.SCHEDULE_NUM = #{scheduleNum} + AND a.DEL_YN = 'N'; ]]> diff --git a/src/main/resources/mapper/RegionMapper.xml b/src/main/resources/mapper/RegionMapper.xml index eef2454..9513361 100644 --- a/src/main/resources/mapper/RegionMapper.xml +++ b/src/main/resources/mapper/RegionMapper.xml @@ -21,4 +21,94 @@ WHERE a.PLAN_NUM = #{planNum}; ]]> + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/ScheduleMapper.xml b/src/main/resources/mapper/ScheduleMapper.xml index 55366e0..d5f9174 100644 --- a/src/main/resources/mapper/ScheduleMapper.xml +++ b/src/main/resources/mapper/ScheduleMapper.xml @@ -5,8 +5,37 @@ + + + + + + + + + + + + + + + + + + + + + From 5c4cc07e513cf775cd6024417b1f354926d58239 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Tue, 25 Nov 2025 20:58:44 +0900 Subject: [PATCH 32/79] =?UTF-8?q?fix:=20=EB=B9=8C=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/barogagi/schedule/controller/ScheduleController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 3d64951..4f9be73 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -1,7 +1,7 @@ package com.barogagi.schedule.controller; -import com.barogagi.member.join.vo.JoinVO; -import com.barogagi.member.join.vo.UserIdCheckVO; +//import com.barogagi.member.join.vo.JoinVO; +//import com.barogagi.member.join.vo.UserIdCheckVO; import com.barogagi.member.login.controller.LoginController; import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.query.service.PlanQueryService; From 576c7cd21b0e8dce00d6adfde62f33c6bfbd3bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EB=8B=A4=EB=AF=BC?= Date: Tue, 25 Nov 2025 21:08:43 +0900 Subject: [PATCH 33/79] =?UTF-8?q?[FIX]=20=EB=B9=8C=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95=20(#20)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 카카오 일정 조회 구현 중 * fix: import 임시 주석 처리 * feat: 일정 등록시 카카오 API 조회 * feat: 카카오 장소 조회 기능 추가 * feat: 네이버 블로그 조회 기능 추가 * feat: 태그번호로 태그명 조회 기능 * feat: ai 추천 로직 진행중 * feat: ai 일정 추천 기능 진행중 * feat: ai 일정 추천 결과 리턴하도록 작업 * chore: 필요 없는 로그 주석처리 * fix: tag 조회 쿼리 테이블 명 대문자로 수정(소문자 사용 시 에러) * chore: AIClient 필요없는 주석 제거 * feat: 주소를 x,y 좌표로 변환하는 api 추가 * feat: ApiResponse에 success/error 정적팩토리 메서드 추가 * feat: 주소 목록 검색 api 추가 * fix: naverBlog 패키지 이름 수정 * fix: tagList를 int 배열이 아니라 map 형태로 보내주도록 수정 * fix: 일정 생성 기능 수정 * feat: 생성된 일정을 DB에 저장하는 API 작업 * update: 일정 상세 조회 API의 태그 조회 쿼리 수정 * feat: 내 일정 목록 조회 API 작업 * update: Plan, Schedule 엔티티에 등록일, 수정일, 삭제여부 컬럼 추가 * update: Plan, Schedule 저장시 삭제여부 함께 저장, 일정 저장 API 메서드명 수정 * feat: 전체 일정 삭제 API 작업 * feat: 일정 수정, 부분삭제 API 작업 * feat: 카카오 장소 키워드 검색 API 추가 * fix: 일정 수정 시 soft-delete 하도록 변경 * feat: 사용자가 직접 추가한 일정, 카카오 API로 검색한 일정 생성할 수 있도록 수정 * fix: 빌드 에러 해결 --- .../com/barogagi/schedule/controller/ScheduleController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 3d64951..4f9be73 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -1,7 +1,7 @@ package com.barogagi.schedule.controller; -import com.barogagi.member.join.vo.JoinVO; -import com.barogagi.member.join.vo.UserIdCheckVO; +//import com.barogagi.member.join.vo.JoinVO; +//import com.barogagi.member.join.vo.UserIdCheckVO; import com.barogagi.member.login.controller.LoginController; import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.query.service.PlanQueryService; From 8b7daf9e13f8b3bd7eebacf2e3e8d7b524bbc0dc Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Sat, 29 Nov 2025 22:58:02 +0900 Subject: [PATCH 34/79] Fix/member api (#21) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FIX 1. 일반 회원가입 관련 API return 값을 swagger에 상세히 작성 2. 회원가입 정보 저장 API 에 아이디 중복 체크 추가 * FIX 1. 약관 정보 등록 API 수정 * FIX 1. 일반 로그인 관련 API 수정 * FIX 1. 회원 정보 수정 API 수정 * FIX 1. oauth, login 관련 코드 수정 * FIX 1. oauth, login 관련 코드 수정 --- .../controller/ApprovalController.java | 25 +++++-- .../com/barogagi/config/JwtAuthFilter.java | 19 +++-- .../com/barogagi/config/SecurityConfig.java | 11 ++- .../controller/MainPageController.java | 24 ++++++- .../basic/join/controller/JoinController.java | 55 ++++++++++++--- .../info/controller/InfoController.java | 69 ++++++++++++++++++- .../member/info/dto/MemberRequestDTO.java | 2 +- .../login/controller/AuthController.java | 16 +---- .../login/controller/LoginController.java | 30 ++++++-- .../member/login/service/AuthService.java | 5 -- .../terms/controller/TermsController.java | 37 +++++++--- .../com/barogagi/terms/dto/TermsAgreeDTO.java | 12 ++++ .../java/com/barogagi/terms/dto/TermsDTO.java | 2 - .../barogagi/terms/dto/TermsProcessDTO.java | 2 - .../barogagi/terms/mapper/TermsMapper.java | 3 +- .../barogagi/terms/service/TermsService.java | 7 +- src/main/resources/mapper/LoginMapper.xml | 2 +- src/main/resources/mapper/TermsMapper.xml | 2 +- 18 files changed, 253 insertions(+), 70 deletions(-) create mode 100644 src/main/java/com/barogagi/terms/dto/TermsAgreeDTO.java diff --git a/src/main/java/com/barogagi/approval/controller/ApprovalController.java b/src/main/java/com/barogagi/approval/controller/ApprovalController.java index 8ab1cba..5806bee 100644 --- a/src/main/java/com/barogagi/approval/controller/ApprovalController.java +++ b/src/main/java/com/barogagi/approval/controller/ApprovalController.java @@ -20,7 +20,7 @@ @Tag(name = "인증", description = "인증 API") @RestController -@RequestMapping("/approval") +@RequestMapping("/approval/tel") public class ApprovalController { private static final Logger logger = LoggerFactory.getLogger(ApprovalController.class); @@ -46,7 +46,15 @@ public ApprovalController(Environment environment){ this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.") + @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "인증번호를 발송할 전화번호를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증번호 발송에 성공하었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "오류가 발생하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "인증번호 발송에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/authCode/send") public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { @@ -131,7 +139,14 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { return apiResponse; } - @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인") + @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호 또는 인증번호 값을 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증이 완료되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "인증이 실패하었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/authCode/check") public ApiResponse approvalTelCheck(@RequestBody ApprovalCompleteVO approvalCompleteVO) { @@ -146,9 +161,9 @@ public ApiResponse approvalTelCheck(@RequestBody ApprovalCompleteVO approvalComp try { if(approvalCompleteVO.getApiSecretKey().equals(API_SECRET_KEY)) { - if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode())){ + if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode()) || inputValidate.isEmpty(approvalCompleteVO.getTel())){ resultCode = "101"; - message = "인증번호를 입력해주세요."; + message = "전화번호 또는 인증번호 값을 입력해주세요."; } else{ logger.info("@@@@ authCode = {}", approvalCompleteVO.getAuthCode()); diff --git a/src/main/java/com/barogagi/config/JwtAuthFilter.java b/src/main/java/com/barogagi/config/JwtAuthFilter.java index 8d9e99f..e1b1fec 100644 --- a/src/main/java/com/barogagi/config/JwtAuthFilter.java +++ b/src/main/java/com/barogagi/config/JwtAuthFilter.java @@ -8,6 +8,8 @@ import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.*; import jakarta.servlet.http.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.JwtException; @@ -18,6 +20,8 @@ @Component public class JwtAuthFilter extends OncePerRequestFilter { + Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class); + private final JwtUtil jwt; private final UserMembershipRepository userRepo; private final MemberService memberService; @@ -34,13 +38,16 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, try { String header = req.getHeader("Authorization"); + + logger.info("@@@ header={}", header); + if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); - + logger.info("@@@ token={}", token); Claims claims = jwt.parseToken(token, "ACCESS"); String membershipNo = jwt.getMembershipNo(claims); - + logger.info("@@@ membershipNo={}", membershipNo); // 회원 조회 Member member = memberService.findByMembershipNo(membershipNo); @@ -55,12 +62,12 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, chain.doFilter(req, res); } catch (ExpiredJwtException e) { // 유효기간이 지나서 만료된 경우 - writeErrorResponse(res, "TOKEN_EXPIRED", "Access token has expired"); + writeErrorResponse(res, "300", "Access token has expired"); } catch (JwtException | SecurityException e) { // 위조되었거나 변조되었거나 구조가 잘못되었을 경우 - writeErrorResponse(res, "REVOKED_TOKEN", "Revoked access token"); + writeErrorResponse(res, "301", "Revoked access token"); } catch (Exception e) { - writeErrorResponse(res, "UNKNOWN_ERROR", "Unknown authentication error"); + writeErrorResponse(res, "302", "Unknown authentication error"); } } @@ -68,7 +75,7 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, @Override protected boolean shouldNotFilter(HttpServletRequest request) { String p = request.getRequestURI(); - return p.startsWith("/auth/"); + return p.startsWith("/auth/") || p.startsWith("/login/basic/membership/userId/search"); } private void writeErrorResponse(HttpServletResponse res, String errorCode, String message) throws IOException { diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index ab8980b..83b3de6 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -3,6 +3,8 @@ import com.barogagi.member.oauth.join.service.CustomOidcUserService; import com.barogagi.member.oauth.join.service.DelegatingOAuth2UserService; import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -16,6 +18,8 @@ @EnableWebSecurity public class SecurityConfig { + Logger logger = LoggerFactory.getLogger(SecurityConfig.class); + private final JwtAuthFilter jwtAuthFilter; private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; @@ -23,6 +27,7 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler) { this.jwtAuthFilter = jwtAuthFilter; this.oAuth2LoginSuccessHandler = oAuth2LoginSuccessHandler; + logger.info("@@ jwtAuthFilter={}", jwtAuthFilter); } private static final String[] PERMIT_URL_ARRAY = { @@ -33,12 +38,14 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, "/webjars/**", "/login/oauth2/**", "/oauth2/**", - "/auth/**", "/login/**", "/membership/join/**", "/terms/**", "/main/page/popular/tag/list", - "main/page/popular/region/list" + "/main/page/popular/region/list", + "/approval/tel/authCode/send", + "/approval/tel/authCode/check", + "/auth/**" }; @Bean diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index 966299d..93dfd66 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -38,7 +38,13 @@ public MainPageController(MainPageService mainPageService, this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "유저 일정 정보 API", description = "메인 화면 - 다가오는 일정 부분에 해당하는 API") + @Operation(summary = "유저 일정 정보 API", description = "메인 화면 - 다가오는 일정 부분에 해당하는 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "일정이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/user/schedule/info") public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { @@ -110,7 +116,13 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { return mainPageResponse; } - @Operation(summary = "인기 태그 조회 API ", description = "메인 화면 - 오늘 많이 생성되는 일정 부분에 해당하는 API") + @Operation(summary = "인기 태그 조회 API ", description = "메인 화면 - 오늘 많이 생성되는 일정 부분에 해당하는 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인기 태그 조회 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 태그 목록이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/popular/tag/list") public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { @@ -155,7 +167,13 @@ public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { return apiResponse; } - @Operation(summary = "인기 지역 조회 API ", description = "메인 화면 - 지금 인기많은 핫 플레이스 부분에 해당하는 API") + @Operation(summary = "인기 지역 조회 API ", description = "메인 화면 - 지금 인기많은 핫 플레이스 부분에 해당하는 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인기 지역 조회 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 지역 목록이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/popular/region/list") public ApiResponse selectPopularRegionList(@RequestBody DefaultVO vo) { diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index b29adc9..783cf97 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -47,7 +47,15 @@ public JoinController(Environment environment, this.passwordConfig = passwordConfig; } - @Operation(summary = "아이디 중복 체크 기능", description = "아이디 중복 체크 기능입니다.") + @Operation(summary = "아이디 중복 체크 기능", description = "아이디 중복 체크 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 아이디 사용이 가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "아이디를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디가 아닙니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "해당 아이디 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/userId/check") public ApiResponse checkUserId(@RequestBody UserIdCheckDTO userIdCheckDTO) { @@ -105,7 +113,16 @@ public ApiResponse checkUserId(@RequestBody UserIdCheckDTO userIdCheckDTO) { return apiResponse; } - @Operation(summary = "회원가입 정보 저장 기능", description = "회원가입 정보 저장 기능입니다.") + @Operation(summary = "회원가입 정보 저장 기능", description = "회원가입 정보 저장 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원가입에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "회원가입에 필요한 정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 아이디에 대한 회원 정보가 이미 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원가입에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/insert") public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestDTO){ @@ -158,15 +175,23 @@ public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestD joinDTO.setNickName(joinRequestDTO.getNickName()); joinDTO.setJoinType("BASIC"); - int insertResult = joinService.insertMembershipInfo(joinDTO); - logger.info("@@ insertResult={}", insertResult); + // 아이디 중복 검증 + int duplicateUserId = joinService.checkUserId(joinDTO); - if(insertResult > 0){ - resultCode = "200"; - message = "회원가입에 성공하였습니다."; - } else{ - resultCode = "300"; - message = "회원가입에 실패하였습니다."; + if(duplicateUserId > 0) { + resultCode = "103"; + message = "해당 아이디에 대한 회원 정보가 이미 존재합니다."; + } else { + int insertResult = joinService.insertMembershipInfo(joinDTO); + logger.info("@@ insertResult={}", insertResult); + + if(insertResult > 0){ + resultCode = "200"; + message = "회원가입에 성공하였습니다."; + } else{ + resultCode = "300"; + message = "회원가입에 실패하였습니다."; + } } } } @@ -188,7 +213,15 @@ public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestD return apiResponse; } - @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API입니다.") + @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "이용 가능한 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "닉네임 데이터를 보내주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합하지 않는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "이미 존재하는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/check/duplicate/nickname") public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index e1b0ea5..4240a1d 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -7,6 +7,7 @@ import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.dto.MemberRequestDTO; import com.barogagi.member.info.service.MemberService; +import com.barogagi.member.login.service.AccountService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import io.swagger.v3.oas.annotations.Operation; @@ -28,6 +29,7 @@ public class InfoController { private final MemberService memberService; private final JoinService joinService; + private final AccountService accountService; private final EncryptUtil encryptUtil; @@ -35,16 +37,24 @@ public class InfoController { public InfoController(MemberService memberService, JoinService joinService, + AccountService accountService, EncryptUtil encryptUtil, PasswordConfig passwordConfig) { this.memberService = memberService; this.joinService = joinService; + this.accountService = accountService; this.encryptUtil = encryptUtil; this.passwordConfig = passwordConfig; } - @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.") + @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 정보 조회가 완료되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/member") public ApiResponse selectMemberInfo(HttpServletRequest request) { logger.info("CALL /info/member"); @@ -97,7 +107,15 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { return apiResponse; } - @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.") + @Operation(summary = "회원 정보 수정", description = "회원 정보 조회 수정입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "이미 해당 닉네임이 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자 정보 수정 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용자 정보 수정 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/member/update") public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody MemberRequestDTO memberRequestDto) { logger.info("CALL /info/member/update"); @@ -198,4 +216,51 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem return apiResponse; } + + @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) + @PostMapping("/member/delete") + public ApiResponse deleteMe(HttpServletRequest request) { + + logger.info("CALL /info/member/delete"); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + Object membershipNoAttr = request.getAttribute("membershipNo"); + logger.info("@@@ membershipNoAttr={}", membershipNoAttr); + if(membershipNoAttr == null) { + throw new MemberInfoException("100", "접근 권한이 존재하지 않습니다."); + } + + String membershipNo = String.valueOf(membershipNoAttr); + + accountService.deleteMyAccount(membershipNo); + + resultCode = "200"; + message = "회원 탈퇴되었습니다."; + + } catch (MemberInfoException ex) { + resultCode = ex.getResultCode(); + message = ex.getMessage(); + + } catch (Exception e) { + logger.error("error", e); + resultCode = "400"; + message = "오류가 발생하였습니다."; + + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); + } + + return apiResponse; + } } diff --git a/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java b/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java index 90c68c7..52a1116 100644 --- a/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java +++ b/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java @@ -6,7 +6,7 @@ @Getter @Setter -public class MemberRequestDTO extends DefaultVO { +public class MemberRequestDTO { // 비밀번호 private String password = ""; diff --git a/src/main/java/com/barogagi/member/login/controller/AuthController.java b/src/main/java/com/barogagi/member/login/controller/AuthController.java index 69da475..a0ee683 100644 --- a/src/main/java/com/barogagi/member/login/controller/AuthController.java +++ b/src/main/java/com/barogagi/member/login/controller/AuthController.java @@ -2,7 +2,6 @@ import com.barogagi.member.login.dto.*; import com.barogagi.member.login.exception.InvalidRefreshTokenException; -import com.barogagi.member.login.service.AccountService; import com.barogagi.member.login.service.AuthService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -10,7 +9,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import java.util.Map; @@ -24,11 +22,10 @@ public class AuthController { private static final Logger logger = LoggerFactory.getLogger(AuthController.class); private final AuthService authService; - private final AccountService accountService; - public AuthController(AuthService authService, AccountService accountService) { + + public AuthController(AuthService authService) { this.authService = authService; - this.accountService = accountService; } @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") @@ -89,14 +86,7 @@ public ResponseEntity> logout( if (refresh != null && !refresh.isBlank()) { authService.logout(refresh); // DB REVOKE } - return ResponseEntity.ok(Map.of("result", "logged_out")); - } - - @DeleteMapping - public ResponseEntity deleteMe(Authentication auth) { - String membershipNo = (String) auth.getPrincipal(); // JwtAuthFilter에서 세팅됨 - accountService.deleteMyAccount(membershipNo); - return ResponseEntity.noContent().build(); // 204 + return ResponseEntity.ok(Map.of("resultCode", "200", "message", "로그아웃 되었습니다.")); } } diff --git a/src/main/java/com/barogagi/member/login/controller/LoginController.java b/src/main/java/com/barogagi/member/login/controller/LoginController.java index 4ae4162..d00ac21 100644 --- a/src/main/java/com/barogagi/member/login/controller/LoginController.java +++ b/src/main/java/com/barogagi/member/login/controller/LoginController.java @@ -70,7 +70,15 @@ public LoginController(Environment environment, this.passwordEncoder = passwordEncoder; } - @Operation(summary = "로그인", description = "로그인 기능입니다. apiSecretKey, userId와 password 값만 보내주세요.") + @Operation(summary = "로그인", description = "로그인 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "로그인에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "로그인이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "로그인에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/login") public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ @@ -101,7 +109,7 @@ public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ logger.info("@@ ok={}", ok); if(!ok) { - throw new LoginException("103", "로그인에 실패하였습니다"); + throw new LoginException("103", "로그인에 실패하였습니다."); } String userId = member.getUserId(); @@ -139,7 +147,14 @@ public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ return apiResponse; } - @Operation(summary = "아이디 찾기 기능", description = "아이디 찾기 기능입니다. apiSecretKey, tel 값만 보내주시면 됩니다.") + @Operation(summary = "아이디 찾기 기능", description = "아이디 찾기 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 전화번호로 가입된 아이디입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "해당 전화번호로 가입된 계정이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/userId/search") public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequestDTO){ @@ -198,7 +213,14 @@ public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequest return apiResponse; } - @Operation(summary = "비밀번호 재설정 기능", description = "비밀번호 재설정 기능입니다. apiSecretKey, userId, password값만 보내주시면 됩니다.") + @Operation(summary = "비밀번호 재설정 기능", description = "비밀번호 재설정 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "비밀번호 재설정에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "아이디, 비밀번호 값이 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "비밀번호 재설정에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/password/update") public ApiResponse updatePassword(@RequestBody LoginDTO vo){ diff --git a/src/main/java/com/barogagi/member/login/service/AuthService.java b/src/main/java/com/barogagi/member/login/service/AuthService.java index 922943e..5133a17 100644 --- a/src/main/java/com/barogagi/member/login/service/AuthService.java +++ b/src/main/java/com/barogagi/member/login/service/AuthService.java @@ -78,11 +78,6 @@ public LoginResponse loginAfterSignup(String userId, String deviceId) { var u = userRepo.findByUserId(userId) .orElseThrow(() -> new RuntimeException("USER_NOT_FOUND")); - // 선택: BASIC 사용자가 이 엔드포인트를 타지 못하게 막고 싶다면 -// if ("BASIC".equalsIgnoreCase(u.getJoinType())) { -// throw new RuntimeException("NOT_OAUTH_MEMBER"); -// } - String no = u.getMembershipNo(); String access = jwt.generateAccessToken(no, u.getUserId()); String refresh = jwt.generateRefreshToken(no, deviceId != null ? deviceId : "web-oauth"); diff --git a/src/main/java/com/barogagi/terms/controller/TermsController.java b/src/main/java/com/barogagi/terms/controller/TermsController.java index 92daee9..a839aa3 100644 --- a/src/main/java/com/barogagi/terms/controller/TermsController.java +++ b/src/main/java/com/barogagi/terms/controller/TermsController.java @@ -3,11 +3,8 @@ import com.barogagi.member.login.dto.LoginVO; import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; +import com.barogagi.terms.dto.*; import com.barogagi.terms.service.TermsService; -import com.barogagi.terms.dto.TermsInputDTO; -import com.barogagi.terms.dto.TermsDTO; -import com.barogagi.terms.dto.TermsOutputDTO; -import com.barogagi.terms.dto.TermsProcessDTO; import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -17,6 +14,7 @@ import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; import java.util.List; @Tag(name = "약관", description = "약관 관련 API") @@ -40,7 +38,14 @@ public TermsController(Environment environment, InputValidate inputValidate, this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다. apiSecretKey와 termsType값만 보내주시면 됩니다.") + @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 조회에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "조회하실 약관의 종류 값이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "약관이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/list") public ApiResponse termsList(@RequestBody TermsInputDTO termsInputDTO){ logger.info("CALL /terms/list"); @@ -89,7 +94,14 @@ public ApiResponse termsList(@RequestBody TermsInputDTO termsInputDTO){ return apiResponse; } - @Operation(summary = "약관 동의 여부 저장", description = "약관 동의 여부 저장 기능입니다.") + @Operation(summary = "약관 동의 여부 저장", description = "약관 동의 여부 저장 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 저장에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "해당 사용자의 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "약관 저장에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/agree/insert") public ApiResponse insertTermsAgree(@RequestBody TermsDTO termsDTO) { logger.info("CALL /agree/insert"); @@ -108,11 +120,20 @@ public ApiResponse insertTermsAgree(@RequestBody TermsDTO termsDTO) { LoginVO loginVO = loginService.findMembershipNo(lvo); if(null != loginVO) { + + List termsAgreeDTOList = new ArrayList<>(); List termsAgreeList = termsDTO.getTermsAgreeList(); + for(TermsProcessDTO termsProcessDTO : termsAgreeList) { - termsProcessDTO.setMembershipNo(loginVO.getMembershipNo()); + + TermsAgreeDTO termsAgreeDTO = new TermsAgreeDTO(); + termsAgreeDTO.setMembershipNo(loginVO.getMembershipNo()); + termsAgreeDTO.setTermsNum(termsProcessDTO.getTermsNum()); + termsAgreeDTO.setAgreeYn(termsProcessDTO.getAgreeYn()); + + termsAgreeDTOList.add(termsAgreeDTO); } - String resCode = termsService.insertTermsAgreeList(termsAgreeList); + String resCode = termsService.insertTermsAgreeList(termsAgreeDTOList); if(resCode.equals("200")) { resultCode = "200"; message = "약관 저장에 성공하였습니다."; diff --git a/src/main/java/com/barogagi/terms/dto/TermsAgreeDTO.java b/src/main/java/com/barogagi/terms/dto/TermsAgreeDTO.java new file mode 100644 index 0000000..1947d9c --- /dev/null +++ b/src/main/java/com/barogagi/terms/dto/TermsAgreeDTO.java @@ -0,0 +1,12 @@ +package com.barogagi.terms.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TermsAgreeDTO { + private String membershipNo = ""; + private int termsNum = 0; + private String agreeYn = ""; +} diff --git a/src/main/java/com/barogagi/terms/dto/TermsDTO.java b/src/main/java/com/barogagi/terms/dto/TermsDTO.java index 37fd45b..e20c5c0 100644 --- a/src/main/java/com/barogagi/terms/dto/TermsDTO.java +++ b/src/main/java/com/barogagi/terms/dto/TermsDTO.java @@ -12,9 +12,7 @@ @Getter @Setter public class TermsDTO extends DefaultVO { - private int termsNum = 0; private String userId = ""; - private String agreeYn = ""; @ArraySchema(schema = @Schema(implementation = TermsProcessDTO.class)) private List termsAgreeList = new ArrayList<>(); diff --git a/src/main/java/com/barogagi/terms/dto/TermsProcessDTO.java b/src/main/java/com/barogagi/terms/dto/TermsProcessDTO.java index 63c2f61..cd6b9fc 100644 --- a/src/main/java/com/barogagi/terms/dto/TermsProcessDTO.java +++ b/src/main/java/com/barogagi/terms/dto/TermsProcessDTO.java @@ -8,7 +8,5 @@ @Setter public class TermsProcessDTO { private int termsNum = 0; - private String userId = ""; private String agreeYn = ""; - private String membershipNo = ""; } diff --git a/src/main/java/com/barogagi/terms/mapper/TermsMapper.java b/src/main/java/com/barogagi/terms/mapper/TermsMapper.java index c12e3d6..2649286 100644 --- a/src/main/java/com/barogagi/terms/mapper/TermsMapper.java +++ b/src/main/java/com/barogagi/terms/mapper/TermsMapper.java @@ -1,5 +1,6 @@ package com.barogagi.terms.mapper; +import com.barogagi.terms.dto.TermsAgreeDTO; import com.barogagi.terms.dto.TermsInputDTO; import com.barogagi.terms.dto.TermsOutputDTO; import com.barogagi.terms.dto.TermsProcessDTO; @@ -12,5 +13,5 @@ public interface TermsMapper { // 사용중인 약관 목록 조회 List selectTermsList(TermsInputDTO termsInputDTO); - int insertTermsAgreeInfo(TermsProcessDTO vo); + int insertTermsAgreeInfo(TermsAgreeDTO vo); } diff --git a/src/main/java/com/barogagi/terms/service/TermsService.java b/src/main/java/com/barogagi/terms/service/TermsService.java index 22c4fbe..6e49b3d 100644 --- a/src/main/java/com/barogagi/terms/service/TermsService.java +++ b/src/main/java/com/barogagi/terms/service/TermsService.java @@ -1,5 +1,6 @@ package com.barogagi.terms.service; +import com.barogagi.terms.dto.TermsAgreeDTO; import com.barogagi.terms.mapper.TermsMapper; import com.barogagi.terms.dto.TermsInputDTO; import com.barogagi.terms.dto.TermsOutputDTO; @@ -27,15 +28,15 @@ public List selectTermsList(TermsInputDTO termsInputDTO) throws } // 약관 동의 여부 저장 - public int insertTermsAgreeInfo(TermsProcessDTO vo) throws Exception { + public int insertTermsAgreeInfo(TermsAgreeDTO vo) throws Exception { return termsMapper.insertTermsAgreeInfo(vo); } @Transactional - public String insertTermsAgreeList(List termsList) { + public String insertTermsAgreeList(List termsList) { String resultCode = ""; try { - for(TermsProcessDTO vo : termsList) { + for(TermsAgreeDTO vo : termsList) { int insertFlag = this.insertTermsAgreeInfo(vo); if(insertFlag > 0){ resultCode = "200"; diff --git a/src/main/resources/mapper/LoginMapper.xml b/src/main/resources/mapper/LoginMapper.xml index 8a32faf..b00464c 100644 --- a/src/main/resources/mapper/LoginMapper.xml +++ b/src/main/resources/mapper/LoginMapper.xml @@ -1,6 +1,6 @@ - + - + Date: Mon, 1 Dec 2025 11:03:50 +0900 Subject: [PATCH 35/79] =?UTF-8?q?[FIX]=20=EC=9D=B8=EA=B8=B0=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=A1=B0=ED=9A=8C=20API=20=EC=88=98=EC=A0=95=20(#2?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 인기 태그 조회 API 수정 - 태그가 최대 10개만 조회되도록 수정 --- src/main/resources/mapper/MainPageMapper.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/resources/mapper/MainPageMapper.xml b/src/main/resources/mapper/MainPageMapper.xml index 0ae09a0..fa93745 100644 --- a/src/main/resources/mapper/MainPageMapper.xml +++ b/src/main/resources/mapper/MainPageMapper.xml @@ -96,6 +96,7 @@ ) tc JOIN tag ti ON ti.TAG_NUM = tc.TAG_NUM ORDER BY rankNo ASC + LIMIT 10 ]]> From 5486521c17bec0fe2e9f495188579b847a40e7f5 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:03:04 +0900 Subject: [PATCH 36/79] =?UTF-8?q?[FIX]=20=ED=9A=8C=EC=9B=90=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B4=80=EB=A0=A8=20API=20=EC=88=98=EC=A0=95=20(#2?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 프로필 이미지 저장되지 않도록 수정 - 프로필 이미지 조회 불가능하도록 수정 - 회원 정보 수정 시 닉네임, 성별, 생년월일만 수정되도록 수정 --- .../member/basic/join/dto/JoinDTO.java | 3 --- .../info/controller/InfoController.java | 26 ------------------- .../com/barogagi/member/info/dto/Member.java | 3 --- .../member/info/dto/MemberRequestDTO.java | 12 --------- .../member/login/entity/UserMembership.java | 3 --- .../join/service/CustomOidcUserService.java | 1 - .../join/service/KakaoOAuth2UserService.java | 10 ------- .../join/service/NaverOAuth2UserService.java | 7 ----- src/main/resources/mapper/JoinMapper.xml | 4 +-- src/main/resources/mapper/MemberMapper.xml | 8 +----- 10 files changed, 3 insertions(+), 74 deletions(-) diff --git a/src/main/java/com/barogagi/member/basic/join/dto/JoinDTO.java b/src/main/java/com/barogagi/member/basic/join/dto/JoinDTO.java index 9b41d74..4e5025a 100644 --- a/src/main/java/com/barogagi/member/basic/join/dto/JoinDTO.java +++ b/src/main/java/com/barogagi/member/basic/join/dto/JoinDTO.java @@ -34,7 +34,4 @@ public class JoinDTO extends DefaultVO { // 회원가입 종류(BASIC : 기본 / GOOGLE : 구글 / KAKAO : 카카오톡 / NAVER : 네이버) private String joinType = ""; - - // 프로필 이미지 - private String profileImg = ""; } diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index 4240a1d..143dfc7 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -126,11 +126,8 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem try { - logger.info("param password={}", memberRequestDto.getPassword()); - logger.info("param email={}", memberRequestDto.getEmail()); logger.info("param gender={}", memberRequestDto.getGender()); logger.info("param nickName={}", memberRequestDto.getNickName()); - logger.info("param tel={}", memberRequestDto.getTel()); String membershipNo = String.valueOf(request.getAttribute("membershipNo")); logger.info("@@ membershipNo.isEmpty()={}", membershipNo.isEmpty()); @@ -144,22 +141,6 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem throw new MemberInfoException("402", "해당 사용자에 대한 정보가 존재하지 않습니다."); } - String joinType = memberInfo.getJoinType(); - logger.info("@@ joinType={}", joinType); - if(joinType.equals("BASIC")) { - // 일반 회원가입으로 가입한 경우에만 비밀번호 수정 가능 - if(!memberRequestDto.getPassword().isEmpty()) { - String encodedPassword = passwordConfig.passwordEncoder().encode(memberRequestDto.getPassword()); - memberInfo.setPassword(encodedPassword); - } - } - - // 이메일 - if(!memberRequestDto.getEmail().isEmpty()) { - String encodedEmail = encryptUtil.encrypt(memberRequestDto.getEmail()); - memberInfo.setEmail(encodedEmail); - } - // 생년월일 if(!memberRequestDto.getBirth().isEmpty()) { memberInfo.setBirth(memberRequestDto.getBirth().replaceAll("[^0-9]", "")); @@ -184,13 +165,6 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem memberInfo.setNickName(memberRequestDto.getNickName()); } - // 프로필 이미지(저장 코드는 회의 진행 후 작업) - - // 전화번호 - if(!memberRequestDto.getTel().isEmpty()) { - memberInfo.setTel(encryptUtil.encrypt(memberRequestDto.getTel().replaceAll("[^0-9]", ""))); - } - int updateMemberInfo = memberService.updateMemberInfo(memberInfo); logger.info("@@ updateMemberInfo={}", updateMemberInfo); if(updateMemberInfo <= 0) { diff --git a/src/main/java/com/barogagi/member/info/dto/Member.java b/src/main/java/com/barogagi/member/info/dto/Member.java index 917725f..3402513 100644 --- a/src/main/java/com/barogagi/member/info/dto/Member.java +++ b/src/main/java/com/barogagi/member/info/dto/Member.java @@ -34,9 +34,6 @@ public class Member { // 회원가입 종류(BASIC : 기본 / GOOGLE : 구글 / KAKAO : 카카오톡 / NAVER : 네이버) private String joinType = ""; - // 프로필 이미지 - private String profileImg = ""; - // 등록일 private String regDate = ""; diff --git a/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java b/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java index 52a1116..dbf2f89 100644 --- a/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java +++ b/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java @@ -8,12 +8,6 @@ @Setter public class MemberRequestDTO { - // 비밀번호 - private String password = ""; - - // 이메일 - private String email = ""; - // 생년월일 private String birth = ""; @@ -22,10 +16,4 @@ public class MemberRequestDTO { // 닉네임 private String nickName = ""; - - // 프로필 이미지 - private String profileImg = ""; - - // 휴대폰 번호 - private String tel = ""; } diff --git a/src/main/java/com/barogagi/member/login/entity/UserMembership.java b/src/main/java/com/barogagi/member/login/entity/UserMembership.java index f62d245..a91478c 100644 --- a/src/main/java/com/barogagi/member/login/entity/UserMembership.java +++ b/src/main/java/com/barogagi/member/login/entity/UserMembership.java @@ -34,9 +34,6 @@ public class UserMembership { @Column(name = "GENDER", length = 1) private String gender; // M / W - @Column(name = "PROFILE_IMG", length = 200) - private String profileImg; - @Column(name = "NICKNAME", length = 100) private String nickname; diff --git a/src/main/java/com/barogagi/member/oauth/join/service/CustomOidcUserService.java b/src/main/java/com/barogagi/member/oauth/join/service/CustomOidcUserService.java index 1281136..8bd6cdc 100644 --- a/src/main/java/com/barogagi/member/oauth/join/service/CustomOidcUserService.java +++ b/src/main/java/com/barogagi/member/oauth/join/service/CustomOidcUserService.java @@ -64,7 +64,6 @@ public org.springframework.security.oauth2.core.oidc.user.OidcUser loadUser( joinDTO.setEmail(encryptUtil.encrypt(email)); joinDTO.setNickName(name); joinDTO.setJoinType("GOOGLE"); - joinDTO.setProfileImg(picture); int insertResult = joinService.insertMembershipInfo(joinDTO); diff --git a/src/main/java/com/barogagi/member/oauth/join/service/KakaoOAuth2UserService.java b/src/main/java/com/barogagi/member/oauth/join/service/KakaoOAuth2UserService.java index 51ddbb9..fbabe9e 100644 --- a/src/main/java/com/barogagi/member/oauth/join/service/KakaoOAuth2UserService.java +++ b/src/main/java/com/barogagi/member/oauth/join/service/KakaoOAuth2UserService.java @@ -50,7 +50,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic String email = ""; String nickName = ""; - String profileImg = ""; if(null != kakaoAccount) { // 이메일 // 계정에 이메일이 존재하는지 (존재 : ture, 미존재 : false) @@ -69,13 +68,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic email = encryptUtil.encrypt((String) kakaoAccount.get("email")); } - // 프로필 - Map profile = (Map) kakaoAccount.get("profile"); - if(null != profile) { - nickName = (String) profile.get("nickname"); - profileImg = (String) profile.get("profile_image_url"); - } - // id에 prefix 추가 id = "provider=kakao" + id; } @@ -98,7 +90,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic dto.setUserId(id); dto.setEmail(email); dto.setNickName(nickName); - dto.setProfileImg(profileImg); dto.setJoinType("KAKAO"); int insertResult = joinService.insertMembershipInfo(dto); @@ -114,7 +105,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic unified.put("id", id); unified.put("email", email != null ? email : ""); unified.put("name", nickName != null ? nickName : ""); - unified.put("picture", profileImg != null ? profileImg : ""); return new DefaultOAuth2User( List.of(new SimpleGrantedAuthority("ROLE_USER")), diff --git a/src/main/java/com/barogagi/member/oauth/join/service/NaverOAuth2UserService.java b/src/main/java/com/barogagi/member/oauth/join/service/NaverOAuth2UserService.java index 9f52ad0..55e1175 100644 --- a/src/main/java/com/barogagi/member/oauth/join/service/NaverOAuth2UserService.java +++ b/src/main/java/com/barogagi/member/oauth/join/service/NaverOAuth2UserService.java @@ -62,7 +62,6 @@ public OAuth2User loadUser(OAuth2UserRequest req) { String id = str(resp.get("id")); String nickName = str(resp.get("nickname")); - String profileImg = str(resp.get("profile_image")); String gender = str(resp.get("gender")); String email = str(resp.get("email")); String birthday = str(resp.get("birthday")); @@ -87,12 +86,6 @@ public OAuth2User loadUser(OAuth2UserRequest req) { joinDTO.setNickName(nickName); joinDTO.setJoinType("NAVER"); - logger.info("@@ profileImg={}", null != profileImg); - if(null != profileImg) { - // 프로필 사진 - joinDTO.setProfileImg(profileImg); - } - // gender(성별) : M(남성), F(여성), U(미설정) logger.info("@@ gender={}", null != gender); if(null != gender) { diff --git a/src/main/resources/mapper/JoinMapper.xml b/src/main/resources/mapper/JoinMapper.xml index be80a85..8acef38 100644 --- a/src/main/resources/mapper/JoinMapper.xml +++ b/src/main/resources/mapper/JoinMapper.xml @@ -3,8 +3,8 @@ diff --git a/src/main/resources/mapper/MemberMapper.xml b/src/main/resources/mapper/MemberMapper.xml index a518aa7..e6cd44c 100644 --- a/src/main/resources/mapper/MemberMapper.xml +++ b/src/main/resources/mapper/MemberMapper.xml @@ -10,7 +10,6 @@ BIRTH as birth, TEL as tel, GENDER as gender, - PROFILE_IMG as profileImg, NICKNAME as nickName, JOIN_TYPE as joinType, REG_DATE as regDate, @@ -31,7 +30,6 @@ BIRTH as birth, TEL as tel, GENDER as gender, - PROFILE_IMG as profileImg, NICKNAME as nickName, JOIN_TYPE as joinType, REG_DATE as regDate, @@ -46,13 +44,9 @@ Date: Wed, 3 Dec 2025 13:42:04 +0900 Subject: [PATCH 37/79] =?UTF-8?q?[FIX]=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#24)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] 회원 탈퇴 코드 수정 - access token이 아닌 refresh token 값으로 회원 탈퇴 처리 * [FIX] 회원 탈퇴 코드 수정 - token 이용해서 db 조회 전에 검증 --- .../info/controller/InfoController.java | 58 +------------- .../login/controller/AuthController.java | 79 +++++++++++++++++-- .../member/login/mapper/AuthMapper.java | 9 +++ .../member/login/service/AccountService.java | 16 +++- .../member/login/service/AuthService.java | 46 ++++++++++- src/main/resources/mapper/AuthMapper.xml | 13 +++ 6 files changed, 153 insertions(+), 68 deletions(-) create mode 100644 src/main/java/com/barogagi/member/login/mapper/AuthMapper.java create mode 100644 src/main/resources/mapper/AuthMapper.xml diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index 143dfc7..f15660e 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -1,13 +1,11 @@ package com.barogagi.member.info.controller; -import com.barogagi.config.PasswordConfig; import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.info.exception.MemberInfoException; import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.dto.MemberRequestDTO; import com.barogagi.member.info.service.MemberService; -import com.barogagi.member.login.service.AccountService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import io.swagger.v3.oas.annotations.Operation; @@ -29,23 +27,16 @@ public class InfoController { private final MemberService memberService; private final JoinService joinService; - private final AccountService accountService; private final EncryptUtil encryptUtil; - private final PasswordConfig passwordConfig; - public InfoController(MemberService memberService, JoinService joinService, - AccountService accountService, - EncryptUtil encryptUtil, - PasswordConfig passwordConfig) { + EncryptUtil encryptUtil) { this.memberService = memberService; this.joinService = joinService; - this.accountService = accountService; this.encryptUtil = encryptUtil; - this.passwordConfig = passwordConfig; } @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.", @@ -190,51 +181,4 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem return apiResponse; } - - @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", - responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "접근 권한이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") - }) - @PostMapping("/member/delete") - public ApiResponse deleteMe(HttpServletRequest request) { - - logger.info("CALL /info/member/delete"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - Object membershipNoAttr = request.getAttribute("membershipNo"); - logger.info("@@@ membershipNoAttr={}", membershipNoAttr); - if(membershipNoAttr == null) { - throw new MemberInfoException("100", "접근 권한이 존재하지 않습니다."); - } - - String membershipNo = String.valueOf(membershipNoAttr); - - accountService.deleteMyAccount(membershipNo); - - resultCode = "200"; - message = "회원 탈퇴되었습니다."; - - } catch (MemberInfoException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; - } } diff --git a/src/main/java/com/barogagi/member/login/controller/AuthController.java b/src/main/java/com/barogagi/member/login/controller/AuthController.java index a0ee683..70dafc1 100644 --- a/src/main/java/com/barogagi/member/login/controller/AuthController.java +++ b/src/main/java/com/barogagi/member/login/controller/AuthController.java @@ -2,7 +2,9 @@ import com.barogagi.member.login.dto.*; import com.barogagi.member.login.exception.InvalidRefreshTokenException; +import com.barogagi.member.login.service.AccountService; import com.barogagi.member.login.service.AuthService; +import com.barogagi.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; @@ -22,18 +24,19 @@ public class AuthController { private static final Logger logger = LoggerFactory.getLogger(AuthController.class); private final AuthService authService; + private final AccountService accountService; - - public AuthController(AuthService authService) { + public AuthController(AuthService authService, + AccountService accountService) { this.authService = authService; + this.accountService = accountService; } @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") @PostMapping("/refresh") public ResponseEntity> refresh( - @RequestHeader(value = "X-Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body, - HttpServletResponse response + @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body ) { logger.info("CALL /auth/refresh"); @@ -70,13 +73,13 @@ public ResponseEntity> refresh( /** * 현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE * 입력 경로: - * - 헤더: X-Refresh-Token: + * - 헤더: Refresh-Token: * - 바디: { "refreshToken": "" } */ @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") @PostMapping("/logout") public ResponseEntity> logout( - @RequestHeader(value = "X-Refresh-Token", required = false) String refreshHeader, + @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, @RequestBody(required = false) Map body ) { String refresh = refreshHeader; @@ -88,6 +91,68 @@ public ResponseEntity> logout( } return ResponseEntity.ok(Map.of("resultCode", "200", "message", "로그아웃 되었습니다.")); } + + @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "refresh token이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) + @PostMapping("/member/delete") + public ApiResponse deleteMe(@RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body) { + + logger.info("CALL /auth/member/delete"); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + String refreshToken = Optional.ofNullable(refreshHeader) + .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) + .orElse(null); + + if (refreshToken == null || refreshToken.isBlank()) { + throw new InvalidRefreshTokenException("100", "refresh token이 존재하지 않습니다."); + } + + // refresh token을 이용해서 membershipNo 구하기 + + Map resultMap = authService.selectUserInfoByToken(refreshToken); + if(!resultMap.get("resultCode").equals("200")) { + throw new InvalidRefreshTokenException(resultMap.get("resultCode"), resultMap.get("message")); + } + + String membershipNo = resultMap.get("membershipNo"); + + int deleteResult = accountService.deleteMyAccount(membershipNo); + if(deleteResult > 0) { + resultCode = "200"; + message = "회원 탈퇴되었습니다."; + } else { + resultCode = "300"; + message = "회원 탈퇴 실패하였습니다."; + } + + } catch (InvalidRefreshTokenException ex) { + resultCode = ex.getCode(); + message = ex.getMessage(); + + } catch (Exception e) { + logger.error("error", e); + resultCode = "400"; + message = "오류가 발생하였습니다."; + + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); + } + + return apiResponse; + } } diff --git a/src/main/java/com/barogagi/member/login/mapper/AuthMapper.java b/src/main/java/com/barogagi/member/login/mapper/AuthMapper.java new file mode 100644 index 0000000..ea6005b --- /dev/null +++ b/src/main/java/com/barogagi/member/login/mapper/AuthMapper.java @@ -0,0 +1,9 @@ +package com.barogagi.member.login.mapper; + +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AuthMapper { + + String selectUserInfoByToken(String refreshToken); +} diff --git a/src/main/java/com/barogagi/member/login/service/AccountService.java b/src/main/java/com/barogagi/member/login/service/AccountService.java index b199387..c473b7f 100644 --- a/src/main/java/com/barogagi/member/login/service/AccountService.java +++ b/src/main/java/com/barogagi/member/login/service/AccountService.java @@ -14,10 +14,20 @@ public class AccountService { private final UserMembershipRepository userMembershipRepository; @Transactional - public void deleteMyAccount(String membershipNo) { + public int deleteMyAccount(String membershipNo) { + + int result = 0; + // 1) 모든 리프레시 토큰 제거 - refreshTokenRepository.deleteAllByMembershipNo(membershipNo); + int deleteRefreshToken = refreshTokenRepository.deleteAllByMembershipNo(membershipNo); + // 2) 회원 삭제 - userMembershipRepository.deleteByMembershipNo(membershipNo); + int deleteByMembershipNo = userMembershipRepository.deleteByMembershipNo(membershipNo); + + if(deleteRefreshToken > 0 && deleteByMembershipNo > 0) { + result = 1; + } + + return result; } } diff --git a/src/main/java/com/barogagi/member/login/service/AuthService.java b/src/main/java/com/barogagi/member/login/service/AuthService.java index 5133a17..78474c2 100644 --- a/src/main/java/com/barogagi/member/login/service/AuthService.java +++ b/src/main/java/com/barogagi/member/login/service/AuthService.java @@ -4,6 +4,7 @@ import com.barogagi.member.login.entity.RefreshToken; import com.barogagi.member.login.entity.UserMembership; import com.barogagi.member.login.exception.InvalidRefreshTokenException; +import com.barogagi.member.login.mapper.AuthMapper; import com.barogagi.member.login.repository.RefreshTokenRepository; import com.barogagi.member.login.repository.UserMembershipRepository; import com.barogagi.util.JwtUtil; @@ -14,7 +15,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,17 +33,21 @@ public class AuthService { private final JwtUtil jwt; private final PasswordEncoder encoder; + private final AuthMapper authMapper; + @Value("${jwt.access-exp-seconds}") private long accessExp; @Value("${jwt.refresh-exp-seconds}") private long refreshExp; public AuthService(UserMembershipRepository userRepo, RefreshTokenRepository refreshRepo, - JwtUtil jwt, PasswordEncoder encoder) { + JwtUtil jwt, PasswordEncoder encoder, + AuthMapper authMapper) { this.userRepo = userRepo; this.refreshRepo = refreshRepo; this.jwt = jwt; this.encoder = encoder; + this.authMapper = authMapper; } public LoginResponse login(LoginRequest req) { @@ -182,5 +190,41 @@ public void logoutAll(String membershipNo) { for (var t : tokens) t.setStatus("REVOKED"); if (!tokens.isEmpty()) refreshRepo.saveAll(tokens); } + + public Map selectUserInfoByToken(String refreshToken) { + + Map returnMap = new HashMap<>(); + + String resultCode = ""; + String message = ""; + String membershipNo = ""; + + try { + // 1. JWT 토큰 유효성 검증 + if(!jwt.isTokenValid(refreshToken) || !jwt.isRefreshToken(refreshToken)) { + throw new InvalidRefreshTokenException("301", "유효하지 않은 refresh token입니다."); + } + + // 2. membershipNo 구하기 + membershipNo = authMapper.selectUserInfoByToken(refreshToken); + + // 3. membershipNo 조회가 되지 않을 경우 + if(membershipNo == null || membershipNo.isBlank()) { + throw new InvalidRefreshTokenException("302", "유효한 token 정보를 찾을 수 없습니다."); + } + + resultCode = "200"; + message = "성공"; + returnMap.put("membershipNo", membershipNo); + + } catch (InvalidRefreshTokenException e) { + resultCode = e.getCode(); + message = e.getMessage(); + } finally { + returnMap.put("resultCode", resultCode); + returnMap.put("message", message); + } + return returnMap; + } } diff --git a/src/main/resources/mapper/AuthMapper.xml b/src/main/resources/mapper/AuthMapper.xml new file mode 100644 index 0000000..00a2901 --- /dev/null +++ b/src/main/resources/mapper/AuthMapper.xml @@ -0,0 +1,13 @@ + + + + + From 3a0e934fad76aab93dbd075fe7024c88e2dc642f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EB=8B=A4=EB=AF=BC?= Date: Sun, 7 Dec 2025 14:21:51 +0900 Subject: [PATCH 38/79] =?UTF-8?q?feat:=20=EC=9D=BC=EC=A0=95=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EC=8B=9C=20=EB=9E=9C=EB=8D=A4=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=EB=A5=BC=20=EC=84=A0=ED=83=9D=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#25)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/barogagi/plan/dto/PlanRegistReqDTO.java | 5 +++++ .../com/barogagi/plan/query/mapper/CategoryMapper.java | 2 ++ .../command/service/ScheduleCommandService.java | 8 ++++++++ .../schedule/controller/ScheduleController.java | 7 +++++-- src/main/resources/mapper/CategoryMapper.xml | 10 ++++++++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java index d26b90b..3f66ab9 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java @@ -3,6 +3,7 @@ import com.barogagi.region.dto.RegionRegistReqDTO; import com.barogagi.tag.dto.TagRegistReqDTO; import io.swagger.v3.oas.annotations.media.ArraySchema; +import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -12,6 +13,7 @@ @Getter @ToString +@Builder(toBuilder = true) @Schema(description = "계획 등록 요청 DTO") public class PlanRegistReqDTO { @@ -33,6 +35,9 @@ public class PlanRegistReqDTO { @Schema(description = "계획 태그 목록") public List planTagRegistReqDTOList; + @Schema(description = "사용자가 랜덤 카테고리를 선택한 경우", example = "Y") + public String isRandomCategory; + // 사용자가 직접 세부일정을 추가한 경우에만 필요한 값 @Schema(description = "사용자가 수동으로 추가한 일정인지 여부(AI 생성 안함)", example = "Y") public String isUserAdded; diff --git a/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java b/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java index f39d2bb..124ad5f 100644 --- a/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java +++ b/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java @@ -10,4 +10,6 @@ public interface CategoryMapper { // 카테고리명 조회 String selectCategoryNmBy(int categoryNum); + + int selectRandomCategoryNum(); } diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index bbd5960..989f08c 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -176,6 +176,14 @@ private PlanRegistResDTO handleAIPlan(ScheduleRegistReqDTO scheduleRegistReqDTO, List> allKakaoPlaceResults = new ArrayList<>(); + // 랜덤 카테고리를 선택한 경우, 카테고리 번호를 랜덤으로 선택 + if (plan.getIsRandomCategory().equals("Y")) { + plan = plan.toBuilder() + .categoryNum(categoryMapper.selectRandomCategoryNum()) + .build(); + logger.info("#$# getCategoryNum={}", plan.getCategoryNum()); + } + String categoryNm = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); String queryString = categoryNm; // 검색어 diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 4f9be73..099ad26 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -104,9 +104,10 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 description = "일정을 생성하는 기능입니다.
" + "- 사용자가 직접 일정을 생성하는 경우는 2가지가 존재합니다.
" + "     CASE 1. 카카오 장소 검색 API를 사용해서 사용자가 가고 싶은 장소를 선택하는 경우, 카카오 장소 검색 API에서 검색한 placeName, placeUrl, addressName을 보내주세요.
" + + "       주의 1) 사용자가 랜덤 카테고리를 선택한 경우 isRandomCategory=\"Y\"로 전달해 주세요. 이때 categoryNum은 전달하지 않아도 됩니다.
" + "     CASE 2. 사용자가 세부일정을 직접 텍스트로 입력하는 경우(ex, 친구집 방문), 세부일정명을 planNm 필드에 담아 보내주세요.
" + - "     주의 1) 반드시 isUserAdded=\"Y\"로 전달해 주세요.
" + - "     주의 2) 사용자가 직접 일정을 생성하는 경우 planTagRegistReqDTOList 값을 전달할 필요는 없습니다.
" + + "       주의 1) 반드시 isUserAdded=\"Y\"로 전달해 주세요.
" + + "       주의 2) 사용자가 직접 일정을 생성하는 경우 planTagRegistReqDTOList 값을 전달할 필요는 없습니다.
" + "- 생성된 일정은 '일정 등록'과정을 거쳐야 DB에 저장됩니다.
" + "- 사용자가 이 API로 생성된 일정을 확인한 후 '일정 생성하기' 버튼을 누르면 '일정 등록' API를 호출해 주세요.") @PostMapping("/create") @@ -134,6 +135,7 @@ public ApiResponse createSchedule( " \"itemNum\": 10,\n" + " \"categoryNum\": 2,\n" + " \"isUserAdded\": \"N\",\n" + + " \"isRandomCategory\": \"Y\",\n" + " \"regionRegistReqDTOList\": [\n" + " { \"regionNum\": 1 }\n" + " ],\n" + @@ -148,6 +150,7 @@ public ApiResponse createSchedule( " \"itemNum\": 2,\n" + " \"categoryNum\": 1,\n" + " \"isUserAdded\": \"Y\",\n" + + " \"isRandomCategory\": \"N\",\n" + " \"userAddedPlaceDTO\": {\n" + " \"placeName\": \"카카오프렌즈 코엑스점\",\n" + " \"placeUrl\": \"http://place.map.kakao.com/26338954\",\n" + diff --git a/src/main/resources/mapper/CategoryMapper.xml b/src/main/resources/mapper/CategoryMapper.xml index 7e71e6e..681a544 100644 --- a/src/main/resources/mapper/CategoryMapper.xml +++ b/src/main/resources/mapper/CategoryMapper.xml @@ -16,4 +16,14 @@ ]]> + + +
From 824677068042fbe4f9f4d0dc1e978343ce9b626a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EB=8B=A4=EB=AF=BC?= Date: Sun, 7 Dec 2025 14:23:47 +0900 Subject: [PATCH 39/79] =?UTF-8?q?[INFRA]=20main=20=EB=B8=8C=EB=9E=9C?= =?UTF-8?q?=EC=B9=98=EB=A1=9C=20merge=20(#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 카카오 일정 조회 구현 중 * fix: import 임시 주석 처리 * feat: 일정 등록시 카카오 API 조회 * feat: 카카오 장소 조회 기능 추가 * feat: 네이버 블로그 조회 기능 추가 * feat: 태그번호로 태그명 조회 기능 * feat: ai 추천 로직 진행중 * feat: ai 일정 추천 기능 진행중 * feat: ai 일정 추천 결과 리턴하도록 작업 * chore: 필요 없는 로그 주석처리 * fix: tag 조회 쿼리 테이블 명 대문자로 수정(소문자 사용 시 에러) * chore: AIClient 필요없는 주석 제거 * feat: 주소를 x,y 좌표로 변환하는 api 추가 * feat: ApiResponse에 success/error 정적팩토리 메서드 추가 * feat: 주소 목록 검색 api 추가 * fix: naverBlog 패키지 이름 수정 * fix: tagList를 int 배열이 아니라 map 형태로 보내주도록 수정 * fix: 일정 생성 기능 수정 * feat: 생성된 일정을 DB에 저장하는 API 작업 * update: 일정 상세 조회 API의 태그 조회 쿼리 수정 * feat: 내 일정 목록 조회 API 작업 * update: Plan, Schedule 엔티티에 등록일, 수정일, 삭제여부 컬럼 추가 * update: Plan, Schedule 저장시 삭제여부 함께 저장, 일정 저장 API 메서드명 수정 * feat: 전체 일정 삭제 API 작업 * feat: 일정 수정, 부분삭제 API 작업 * feat: 카카오 장소 키워드 검색 API 추가 * fix: 일정 수정 시 soft-delete 하도록 변경 * feat: 사용자가 직접 추가한 일정, 카카오 API로 검색한 일정 생성할 수 있도록 수정 * fix: 빌드 에러 해결 * Fix/member api (#21) * FIX 1. 일반 회원가입 관련 API return 값을 swagger에 상세히 작성 2. 회원가입 정보 저장 API 에 아이디 중복 체크 추가 * FIX 1. 약관 정보 등록 API 수정 * FIX 1. 일반 로그인 관련 API 수정 * FIX 1. 회원 정보 수정 API 수정 * FIX 1. oauth, login 관련 코드 수정 * FIX 1. oauth, login 관련 코드 수정 * [FIX] 인기 태그 조회 API 수정 (#22) 1. 인기 태그 조회 API 수정 - 태그가 최대 10개만 조회되도록 수정 * [FIX] 회원 정보 관련 API 수정 (#23) - 프로필 이미지 저장되지 않도록 수정 - 프로필 이미지 조회 불가능하도록 수정 - 회원 정보 수정 시 닉네임, 성별, 생년월일만 수정되도록 수정 * [FIX] 회원 탈퇴 코드 수정 (#24) * [FIX] 회원 탈퇴 코드 수정 - access token이 아닌 refresh token 값으로 회원 탈퇴 처리 * [FIX] 회원 탈퇴 코드 수정 - token 이용해서 db 조회 전에 검증 * feat: 일정 생성 시 랜덤 카테고리를 선택할 수 있도록 수정 (#25) --------- Co-authored-by: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> --- .../controller/ApprovalController.java | 25 ++++-- .../com/barogagi/config/JwtAuthFilter.java | 19 +++-- .../com/barogagi/config/SecurityConfig.java | 11 ++- .../controller/MainPageController.java | 24 +++++- .../basic/join/controller/JoinController.java | 55 ++++++++++--- .../member/basic/join/dto/JoinDTO.java | 3 - .../info/controller/InfoController.java | 51 ++++-------- .../com/barogagi/member/info/dto/Member.java | 3 - .../member/info/dto/MemberRequestDTO.java | 14 +--- .../login/controller/AuthController.java | 81 ++++++++++++++++--- .../login/controller/LoginController.java | 30 ++++++- .../member/login/entity/UserMembership.java | 3 - .../member/login/mapper/AuthMapper.java | 9 +++ .../member/login/service/AccountService.java | 16 +++- .../member/login/service/AuthService.java | 51 ++++++++++-- .../join/service/CustomOidcUserService.java | 1 - .../join/service/KakaoOAuth2UserService.java | 10 --- .../join/service/NaverOAuth2UserService.java | 7 -- .../barogagi/plan/dto/PlanRegistReqDTO.java | 5 ++ .../plan/query/mapper/CategoryMapper.java | 2 + .../service/ScheduleCommandService.java | 8 ++ .../controller/ScheduleController.java | 7 +- .../terms/controller/TermsController.java | 37 +++++++-- .../com/barogagi/terms/dto/TermsAgreeDTO.java | 12 +++ .../java/com/barogagi/terms/dto/TermsDTO.java | 2 - .../barogagi/terms/dto/TermsProcessDTO.java | 2 - .../barogagi/terms/mapper/TermsMapper.java | 3 +- .../barogagi/terms/service/TermsService.java | 7 +- src/main/resources/mapper/AuthMapper.xml | 13 +++ src/main/resources/mapper/CategoryMapper.xml | 10 +++ src/main/resources/mapper/JoinMapper.xml | 4 +- src/main/resources/mapper/LoginMapper.xml | 2 +- src/main/resources/mapper/MainPageMapper.xml | 1 + src/main/resources/mapper/MemberMapper.xml | 8 +- src/main/resources/mapper/TermsMapper.xml | 2 +- 35 files changed, 382 insertions(+), 156 deletions(-) create mode 100644 src/main/java/com/barogagi/member/login/mapper/AuthMapper.java create mode 100644 src/main/java/com/barogagi/terms/dto/TermsAgreeDTO.java create mode 100644 src/main/resources/mapper/AuthMapper.xml diff --git a/src/main/java/com/barogagi/approval/controller/ApprovalController.java b/src/main/java/com/barogagi/approval/controller/ApprovalController.java index 8ab1cba..5806bee 100644 --- a/src/main/java/com/barogagi/approval/controller/ApprovalController.java +++ b/src/main/java/com/barogagi/approval/controller/ApprovalController.java @@ -20,7 +20,7 @@ @Tag(name = "인증", description = "인증 API") @RestController -@RequestMapping("/approval") +@RequestMapping("/approval/tel") public class ApprovalController { private static final Logger logger = LoggerFactory.getLogger(ApprovalController.class); @@ -46,7 +46,15 @@ public ApprovalController(Environment environment){ this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.") + @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "인증번호를 발송할 전화번호를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증번호 발송에 성공하었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "오류가 발생하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "인증번호 발송에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/authCode/send") public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { @@ -131,7 +139,14 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { return apiResponse; } - @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인") + @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호 또는 인증번호 값을 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증이 완료되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "인증이 실패하었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/authCode/check") public ApiResponse approvalTelCheck(@RequestBody ApprovalCompleteVO approvalCompleteVO) { @@ -146,9 +161,9 @@ public ApiResponse approvalTelCheck(@RequestBody ApprovalCompleteVO approvalComp try { if(approvalCompleteVO.getApiSecretKey().equals(API_SECRET_KEY)) { - if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode())){ + if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode()) || inputValidate.isEmpty(approvalCompleteVO.getTel())){ resultCode = "101"; - message = "인증번호를 입력해주세요."; + message = "전화번호 또는 인증번호 값을 입력해주세요."; } else{ logger.info("@@@@ authCode = {}", approvalCompleteVO.getAuthCode()); diff --git a/src/main/java/com/barogagi/config/JwtAuthFilter.java b/src/main/java/com/barogagi/config/JwtAuthFilter.java index 8d9e99f..e1b1fec 100644 --- a/src/main/java/com/barogagi/config/JwtAuthFilter.java +++ b/src/main/java/com/barogagi/config/JwtAuthFilter.java @@ -8,6 +8,8 @@ import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.*; import jakarta.servlet.http.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.JwtException; @@ -18,6 +20,8 @@ @Component public class JwtAuthFilter extends OncePerRequestFilter { + Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class); + private final JwtUtil jwt; private final UserMembershipRepository userRepo; private final MemberService memberService; @@ -34,13 +38,16 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, try { String header = req.getHeader("Authorization"); + + logger.info("@@@ header={}", header); + if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); - + logger.info("@@@ token={}", token); Claims claims = jwt.parseToken(token, "ACCESS"); String membershipNo = jwt.getMembershipNo(claims); - + logger.info("@@@ membershipNo={}", membershipNo); // 회원 조회 Member member = memberService.findByMembershipNo(membershipNo); @@ -55,12 +62,12 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, chain.doFilter(req, res); } catch (ExpiredJwtException e) { // 유효기간이 지나서 만료된 경우 - writeErrorResponse(res, "TOKEN_EXPIRED", "Access token has expired"); + writeErrorResponse(res, "300", "Access token has expired"); } catch (JwtException | SecurityException e) { // 위조되었거나 변조되었거나 구조가 잘못되었을 경우 - writeErrorResponse(res, "REVOKED_TOKEN", "Revoked access token"); + writeErrorResponse(res, "301", "Revoked access token"); } catch (Exception e) { - writeErrorResponse(res, "UNKNOWN_ERROR", "Unknown authentication error"); + writeErrorResponse(res, "302", "Unknown authentication error"); } } @@ -68,7 +75,7 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, @Override protected boolean shouldNotFilter(HttpServletRequest request) { String p = request.getRequestURI(); - return p.startsWith("/auth/"); + return p.startsWith("/auth/") || p.startsWith("/login/basic/membership/userId/search"); } private void writeErrorResponse(HttpServletResponse res, String errorCode, String message) throws IOException { diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index ab8980b..83b3de6 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -3,6 +3,8 @@ import com.barogagi.member.oauth.join.service.CustomOidcUserService; import com.barogagi.member.oauth.join.service.DelegatingOAuth2UserService; import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -16,6 +18,8 @@ @EnableWebSecurity public class SecurityConfig { + Logger logger = LoggerFactory.getLogger(SecurityConfig.class); + private final JwtAuthFilter jwtAuthFilter; private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; @@ -23,6 +27,7 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler) { this.jwtAuthFilter = jwtAuthFilter; this.oAuth2LoginSuccessHandler = oAuth2LoginSuccessHandler; + logger.info("@@ jwtAuthFilter={}", jwtAuthFilter); } private static final String[] PERMIT_URL_ARRAY = { @@ -33,12 +38,14 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, "/webjars/**", "/login/oauth2/**", "/oauth2/**", - "/auth/**", "/login/**", "/membership/join/**", "/terms/**", "/main/page/popular/tag/list", - "main/page/popular/region/list" + "/main/page/popular/region/list", + "/approval/tel/authCode/send", + "/approval/tel/authCode/check", + "/auth/**" }; @Bean diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index 966299d..93dfd66 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -38,7 +38,13 @@ public MainPageController(MainPageService mainPageService, this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "유저 일정 정보 API", description = "메인 화면 - 다가오는 일정 부분에 해당하는 API") + @Operation(summary = "유저 일정 정보 API", description = "메인 화면 - 다가오는 일정 부분에 해당하는 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "일정이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/user/schedule/info") public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { @@ -110,7 +116,13 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { return mainPageResponse; } - @Operation(summary = "인기 태그 조회 API ", description = "메인 화면 - 오늘 많이 생성되는 일정 부분에 해당하는 API") + @Operation(summary = "인기 태그 조회 API ", description = "메인 화면 - 오늘 많이 생성되는 일정 부분에 해당하는 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인기 태그 조회 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 태그 목록이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/popular/tag/list") public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { @@ -155,7 +167,13 @@ public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { return apiResponse; } - @Operation(summary = "인기 지역 조회 API ", description = "메인 화면 - 지금 인기많은 핫 플레이스 부분에 해당하는 API") + @Operation(summary = "인기 지역 조회 API ", description = "메인 화면 - 지금 인기많은 핫 플레이스 부분에 해당하는 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인기 지역 조회 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 지역 목록이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/popular/region/list") public ApiResponse selectPopularRegionList(@RequestBody DefaultVO vo) { diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index b29adc9..783cf97 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -47,7 +47,15 @@ public JoinController(Environment environment, this.passwordConfig = passwordConfig; } - @Operation(summary = "아이디 중복 체크 기능", description = "아이디 중복 체크 기능입니다.") + @Operation(summary = "아이디 중복 체크 기능", description = "아이디 중복 체크 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 아이디 사용이 가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "아이디를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디가 아닙니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "해당 아이디 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/userId/check") public ApiResponse checkUserId(@RequestBody UserIdCheckDTO userIdCheckDTO) { @@ -105,7 +113,16 @@ public ApiResponse checkUserId(@RequestBody UserIdCheckDTO userIdCheckDTO) { return apiResponse; } - @Operation(summary = "회원가입 정보 저장 기능", description = "회원가입 정보 저장 기능입니다.") + @Operation(summary = "회원가입 정보 저장 기능", description = "회원가입 정보 저장 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원가입에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "회원가입에 필요한 정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 아이디에 대한 회원 정보가 이미 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원가입에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/insert") public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestDTO){ @@ -158,15 +175,23 @@ public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestD joinDTO.setNickName(joinRequestDTO.getNickName()); joinDTO.setJoinType("BASIC"); - int insertResult = joinService.insertMembershipInfo(joinDTO); - logger.info("@@ insertResult={}", insertResult); + // 아이디 중복 검증 + int duplicateUserId = joinService.checkUserId(joinDTO); - if(insertResult > 0){ - resultCode = "200"; - message = "회원가입에 성공하였습니다."; - } else{ - resultCode = "300"; - message = "회원가입에 실패하였습니다."; + if(duplicateUserId > 0) { + resultCode = "103"; + message = "해당 아이디에 대한 회원 정보가 이미 존재합니다."; + } else { + int insertResult = joinService.insertMembershipInfo(joinDTO); + logger.info("@@ insertResult={}", insertResult); + + if(insertResult > 0){ + resultCode = "200"; + message = "회원가입에 성공하였습니다."; + } else{ + resultCode = "300"; + message = "회원가입에 실패하였습니다."; + } } } } @@ -188,7 +213,15 @@ public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestD return apiResponse; } - @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API입니다.") + @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "이용 가능한 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "닉네임 데이터를 보내주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합하지 않는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "이미 존재하는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/check/duplicate/nickname") public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ diff --git a/src/main/java/com/barogagi/member/basic/join/dto/JoinDTO.java b/src/main/java/com/barogagi/member/basic/join/dto/JoinDTO.java index 9b41d74..4e5025a 100644 --- a/src/main/java/com/barogagi/member/basic/join/dto/JoinDTO.java +++ b/src/main/java/com/barogagi/member/basic/join/dto/JoinDTO.java @@ -34,7 +34,4 @@ public class JoinDTO extends DefaultVO { // 회원가입 종류(BASIC : 기본 / GOOGLE : 구글 / KAKAO : 카카오톡 / NAVER : 네이버) private String joinType = ""; - - // 프로필 이미지 - private String profileImg = ""; } diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index e1b0ea5..f15660e 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -1,6 +1,5 @@ package com.barogagi.member.info.controller; -import com.barogagi.config.PasswordConfig; import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.info.exception.MemberInfoException; @@ -31,20 +30,22 @@ public class InfoController { private final EncryptUtil encryptUtil; - private final PasswordConfig passwordConfig; - public InfoController(MemberService memberService, JoinService joinService, - EncryptUtil encryptUtil, - PasswordConfig passwordConfig) { + EncryptUtil encryptUtil) { this.memberService = memberService; this.joinService = joinService; this.encryptUtil = encryptUtil; - this.passwordConfig = passwordConfig; } - @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.") + @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 정보 조회가 완료되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/member") public ApiResponse selectMemberInfo(HttpServletRequest request) { logger.info("CALL /info/member"); @@ -97,7 +98,15 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { return apiResponse; } - @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.") + @Operation(summary = "회원 정보 수정", description = "회원 정보 조회 수정입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "이미 해당 닉네임이 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자 정보 수정 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용자 정보 수정 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/member/update") public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody MemberRequestDTO memberRequestDto) { logger.info("CALL /info/member/update"); @@ -108,11 +117,8 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem try { - logger.info("param password={}", memberRequestDto.getPassword()); - logger.info("param email={}", memberRequestDto.getEmail()); logger.info("param gender={}", memberRequestDto.getGender()); logger.info("param nickName={}", memberRequestDto.getNickName()); - logger.info("param tel={}", memberRequestDto.getTel()); String membershipNo = String.valueOf(request.getAttribute("membershipNo")); logger.info("@@ membershipNo.isEmpty()={}", membershipNo.isEmpty()); @@ -126,22 +132,6 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem throw new MemberInfoException("402", "해당 사용자에 대한 정보가 존재하지 않습니다."); } - String joinType = memberInfo.getJoinType(); - logger.info("@@ joinType={}", joinType); - if(joinType.equals("BASIC")) { - // 일반 회원가입으로 가입한 경우에만 비밀번호 수정 가능 - if(!memberRequestDto.getPassword().isEmpty()) { - String encodedPassword = passwordConfig.passwordEncoder().encode(memberRequestDto.getPassword()); - memberInfo.setPassword(encodedPassword); - } - } - - // 이메일 - if(!memberRequestDto.getEmail().isEmpty()) { - String encodedEmail = encryptUtil.encrypt(memberRequestDto.getEmail()); - memberInfo.setEmail(encodedEmail); - } - // 생년월일 if(!memberRequestDto.getBirth().isEmpty()) { memberInfo.setBirth(memberRequestDto.getBirth().replaceAll("[^0-9]", "")); @@ -166,13 +156,6 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem memberInfo.setNickName(memberRequestDto.getNickName()); } - // 프로필 이미지(저장 코드는 회의 진행 후 작업) - - // 전화번호 - if(!memberRequestDto.getTel().isEmpty()) { - memberInfo.setTel(encryptUtil.encrypt(memberRequestDto.getTel().replaceAll("[^0-9]", ""))); - } - int updateMemberInfo = memberService.updateMemberInfo(memberInfo); logger.info("@@ updateMemberInfo={}", updateMemberInfo); if(updateMemberInfo <= 0) { diff --git a/src/main/java/com/barogagi/member/info/dto/Member.java b/src/main/java/com/barogagi/member/info/dto/Member.java index 917725f..3402513 100644 --- a/src/main/java/com/barogagi/member/info/dto/Member.java +++ b/src/main/java/com/barogagi/member/info/dto/Member.java @@ -34,9 +34,6 @@ public class Member { // 회원가입 종류(BASIC : 기본 / GOOGLE : 구글 / KAKAO : 카카오톡 / NAVER : 네이버) private String joinType = ""; - // 프로필 이미지 - private String profileImg = ""; - // 등록일 private String regDate = ""; diff --git a/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java b/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java index 90c68c7..dbf2f89 100644 --- a/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java +++ b/src/main/java/com/barogagi/member/info/dto/MemberRequestDTO.java @@ -6,13 +6,7 @@ @Getter @Setter -public class MemberRequestDTO extends DefaultVO { - - // 비밀번호 - private String password = ""; - - // 이메일 - private String email = ""; +public class MemberRequestDTO { // 생년월일 private String birth = ""; @@ -22,10 +16,4 @@ public class MemberRequestDTO extends DefaultVO { // 닉네임 private String nickName = ""; - - // 프로필 이미지 - private String profileImg = ""; - - // 휴대폰 번호 - private String tel = ""; } diff --git a/src/main/java/com/barogagi/member/login/controller/AuthController.java b/src/main/java/com/barogagi/member/login/controller/AuthController.java index 69da475..70dafc1 100644 --- a/src/main/java/com/barogagi/member/login/controller/AuthController.java +++ b/src/main/java/com/barogagi/member/login/controller/AuthController.java @@ -4,13 +4,13 @@ import com.barogagi.member.login.exception.InvalidRefreshTokenException; import com.barogagi.member.login.service.AccountService; import com.barogagi.member.login.service.AuthService; +import com.barogagi.response.ApiResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import java.util.Map; @@ -26,7 +26,8 @@ public class AuthController { private final AuthService authService; private final AccountService accountService; - public AuthController(AuthService authService, AccountService accountService) { + public AuthController(AuthService authService, + AccountService accountService) { this.authService = authService; this.accountService = accountService; } @@ -34,9 +35,8 @@ public AuthController(AuthService authService, AccountService accountService) { @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") @PostMapping("/refresh") public ResponseEntity> refresh( - @RequestHeader(value = "X-Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body, - HttpServletResponse response + @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body ) { logger.info("CALL /auth/refresh"); @@ -73,13 +73,13 @@ public ResponseEntity> refresh( /** * 현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE * 입력 경로: - * - 헤더: X-Refresh-Token: + * - 헤더: Refresh-Token: * - 바디: { "refreshToken": "" } */ @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") @PostMapping("/logout") public ResponseEntity> logout( - @RequestHeader(value = "X-Refresh-Token", required = false) String refreshHeader, + @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, @RequestBody(required = false) Map body ) { String refresh = refreshHeader; @@ -89,14 +89,69 @@ public ResponseEntity> logout( if (refresh != null && !refresh.isBlank()) { authService.logout(refresh); // DB REVOKE } - return ResponseEntity.ok(Map.of("result", "logged_out")); + return ResponseEntity.ok(Map.of("resultCode", "200", "message", "로그아웃 되었습니다.")); } - @DeleteMapping - public ResponseEntity deleteMe(Authentication auth) { - String membershipNo = (String) auth.getPrincipal(); // JwtAuthFilter에서 세팅됨 - accountService.deleteMyAccount(membershipNo); - return ResponseEntity.noContent().build(); // 204 + @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "refresh token이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) + @PostMapping("/member/delete") + public ApiResponse deleteMe(@RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body) { + + logger.info("CALL /auth/member/delete"); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + String refreshToken = Optional.ofNullable(refreshHeader) + .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) + .orElse(null); + + if (refreshToken == null || refreshToken.isBlank()) { + throw new InvalidRefreshTokenException("100", "refresh token이 존재하지 않습니다."); + } + + // refresh token을 이용해서 membershipNo 구하기 + + Map resultMap = authService.selectUserInfoByToken(refreshToken); + if(!resultMap.get("resultCode").equals("200")) { + throw new InvalidRefreshTokenException(resultMap.get("resultCode"), resultMap.get("message")); + } + + String membershipNo = resultMap.get("membershipNo"); + + int deleteResult = accountService.deleteMyAccount(membershipNo); + if(deleteResult > 0) { + resultCode = "200"; + message = "회원 탈퇴되었습니다."; + } else { + resultCode = "300"; + message = "회원 탈퇴 실패하였습니다."; + } + + } catch (InvalidRefreshTokenException ex) { + resultCode = ex.getCode(); + message = ex.getMessage(); + + } catch (Exception e) { + logger.error("error", e); + resultCode = "400"; + message = "오류가 발생하였습니다."; + + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); + } + + return apiResponse; } } diff --git a/src/main/java/com/barogagi/member/login/controller/LoginController.java b/src/main/java/com/barogagi/member/login/controller/LoginController.java index 4ae4162..d00ac21 100644 --- a/src/main/java/com/barogagi/member/login/controller/LoginController.java +++ b/src/main/java/com/barogagi/member/login/controller/LoginController.java @@ -70,7 +70,15 @@ public LoginController(Environment environment, this.passwordEncoder = passwordEncoder; } - @Operation(summary = "로그인", description = "로그인 기능입니다. apiSecretKey, userId와 password 값만 보내주세요.") + @Operation(summary = "로그인", description = "로그인 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "로그인에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "로그인이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "로그인에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/login") public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ @@ -101,7 +109,7 @@ public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ logger.info("@@ ok={}", ok); if(!ok) { - throw new LoginException("103", "로그인에 실패하였습니다"); + throw new LoginException("103", "로그인에 실패하였습니다."); } String userId = member.getUserId(); @@ -139,7 +147,14 @@ public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ return apiResponse; } - @Operation(summary = "아이디 찾기 기능", description = "아이디 찾기 기능입니다. apiSecretKey, tel 값만 보내주시면 됩니다.") + @Operation(summary = "아이디 찾기 기능", description = "아이디 찾기 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 전화번호로 가입된 아이디입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "해당 전화번호로 가입된 계정이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/userId/search") public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequestDTO){ @@ -198,7 +213,14 @@ public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequest return apiResponse; } - @Operation(summary = "비밀번호 재설정 기능", description = "비밀번호 재설정 기능입니다. apiSecretKey, userId, password값만 보내주시면 됩니다.") + @Operation(summary = "비밀번호 재설정 기능", description = "비밀번호 재설정 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "비밀번호 재설정에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "아이디, 비밀번호 값이 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "비밀번호 재설정에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/basic/membership/password/update") public ApiResponse updatePassword(@RequestBody LoginDTO vo){ diff --git a/src/main/java/com/barogagi/member/login/entity/UserMembership.java b/src/main/java/com/barogagi/member/login/entity/UserMembership.java index f62d245..a91478c 100644 --- a/src/main/java/com/barogagi/member/login/entity/UserMembership.java +++ b/src/main/java/com/barogagi/member/login/entity/UserMembership.java @@ -34,9 +34,6 @@ public class UserMembership { @Column(name = "GENDER", length = 1) private String gender; // M / W - @Column(name = "PROFILE_IMG", length = 200) - private String profileImg; - @Column(name = "NICKNAME", length = 100) private String nickname; diff --git a/src/main/java/com/barogagi/member/login/mapper/AuthMapper.java b/src/main/java/com/barogagi/member/login/mapper/AuthMapper.java new file mode 100644 index 0000000..ea6005b --- /dev/null +++ b/src/main/java/com/barogagi/member/login/mapper/AuthMapper.java @@ -0,0 +1,9 @@ +package com.barogagi.member.login.mapper; + +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AuthMapper { + + String selectUserInfoByToken(String refreshToken); +} diff --git a/src/main/java/com/barogagi/member/login/service/AccountService.java b/src/main/java/com/barogagi/member/login/service/AccountService.java index b199387..c473b7f 100644 --- a/src/main/java/com/barogagi/member/login/service/AccountService.java +++ b/src/main/java/com/barogagi/member/login/service/AccountService.java @@ -14,10 +14,20 @@ public class AccountService { private final UserMembershipRepository userMembershipRepository; @Transactional - public void deleteMyAccount(String membershipNo) { + public int deleteMyAccount(String membershipNo) { + + int result = 0; + // 1) 모든 리프레시 토큰 제거 - refreshTokenRepository.deleteAllByMembershipNo(membershipNo); + int deleteRefreshToken = refreshTokenRepository.deleteAllByMembershipNo(membershipNo); + // 2) 회원 삭제 - userMembershipRepository.deleteByMembershipNo(membershipNo); + int deleteByMembershipNo = userMembershipRepository.deleteByMembershipNo(membershipNo); + + if(deleteRefreshToken > 0 && deleteByMembershipNo > 0) { + result = 1; + } + + return result; } } diff --git a/src/main/java/com/barogagi/member/login/service/AuthService.java b/src/main/java/com/barogagi/member/login/service/AuthService.java index 922943e..78474c2 100644 --- a/src/main/java/com/barogagi/member/login/service/AuthService.java +++ b/src/main/java/com/barogagi/member/login/service/AuthService.java @@ -4,6 +4,7 @@ import com.barogagi.member.login.entity.RefreshToken; import com.barogagi.member.login.entity.UserMembership; import com.barogagi.member.login.exception.InvalidRefreshTokenException; +import com.barogagi.member.login.mapper.AuthMapper; import com.barogagi.member.login.repository.RefreshTokenRepository; import com.barogagi.member.login.repository.UserMembershipRepository; import com.barogagi.util.JwtUtil; @@ -14,7 +15,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; +import java.util.Map; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -29,17 +33,21 @@ public class AuthService { private final JwtUtil jwt; private final PasswordEncoder encoder; + private final AuthMapper authMapper; + @Value("${jwt.access-exp-seconds}") private long accessExp; @Value("${jwt.refresh-exp-seconds}") private long refreshExp; public AuthService(UserMembershipRepository userRepo, RefreshTokenRepository refreshRepo, - JwtUtil jwt, PasswordEncoder encoder) { + JwtUtil jwt, PasswordEncoder encoder, + AuthMapper authMapper) { this.userRepo = userRepo; this.refreshRepo = refreshRepo; this.jwt = jwt; this.encoder = encoder; + this.authMapper = authMapper; } public LoginResponse login(LoginRequest req) { @@ -78,11 +86,6 @@ public LoginResponse loginAfterSignup(String userId, String deviceId) { var u = userRepo.findByUserId(userId) .orElseThrow(() -> new RuntimeException("USER_NOT_FOUND")); - // 선택: BASIC 사용자가 이 엔드포인트를 타지 못하게 막고 싶다면 -// if ("BASIC".equalsIgnoreCase(u.getJoinType())) { -// throw new RuntimeException("NOT_OAUTH_MEMBER"); -// } - String no = u.getMembershipNo(); String access = jwt.generateAccessToken(no, u.getUserId()); String refresh = jwt.generateRefreshToken(no, deviceId != null ? deviceId : "web-oauth"); @@ -187,5 +190,41 @@ public void logoutAll(String membershipNo) { for (var t : tokens) t.setStatus("REVOKED"); if (!tokens.isEmpty()) refreshRepo.saveAll(tokens); } + + public Map selectUserInfoByToken(String refreshToken) { + + Map returnMap = new HashMap<>(); + + String resultCode = ""; + String message = ""; + String membershipNo = ""; + + try { + // 1. JWT 토큰 유효성 검증 + if(!jwt.isTokenValid(refreshToken) || !jwt.isRefreshToken(refreshToken)) { + throw new InvalidRefreshTokenException("301", "유효하지 않은 refresh token입니다."); + } + + // 2. membershipNo 구하기 + membershipNo = authMapper.selectUserInfoByToken(refreshToken); + + // 3. membershipNo 조회가 되지 않을 경우 + if(membershipNo == null || membershipNo.isBlank()) { + throw new InvalidRefreshTokenException("302", "유효한 token 정보를 찾을 수 없습니다."); + } + + resultCode = "200"; + message = "성공"; + returnMap.put("membershipNo", membershipNo); + + } catch (InvalidRefreshTokenException e) { + resultCode = e.getCode(); + message = e.getMessage(); + } finally { + returnMap.put("resultCode", resultCode); + returnMap.put("message", message); + } + return returnMap; + } } diff --git a/src/main/java/com/barogagi/member/oauth/join/service/CustomOidcUserService.java b/src/main/java/com/barogagi/member/oauth/join/service/CustomOidcUserService.java index 1281136..8bd6cdc 100644 --- a/src/main/java/com/barogagi/member/oauth/join/service/CustomOidcUserService.java +++ b/src/main/java/com/barogagi/member/oauth/join/service/CustomOidcUserService.java @@ -64,7 +64,6 @@ public org.springframework.security.oauth2.core.oidc.user.OidcUser loadUser( joinDTO.setEmail(encryptUtil.encrypt(email)); joinDTO.setNickName(name); joinDTO.setJoinType("GOOGLE"); - joinDTO.setProfileImg(picture); int insertResult = joinService.insertMembershipInfo(joinDTO); diff --git a/src/main/java/com/barogagi/member/oauth/join/service/KakaoOAuth2UserService.java b/src/main/java/com/barogagi/member/oauth/join/service/KakaoOAuth2UserService.java index 51ddbb9..fbabe9e 100644 --- a/src/main/java/com/barogagi/member/oauth/join/service/KakaoOAuth2UserService.java +++ b/src/main/java/com/barogagi/member/oauth/join/service/KakaoOAuth2UserService.java @@ -50,7 +50,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic String email = ""; String nickName = ""; - String profileImg = ""; if(null != kakaoAccount) { // 이메일 // 계정에 이메일이 존재하는지 (존재 : ture, 미존재 : false) @@ -69,13 +68,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic email = encryptUtil.encrypt((String) kakaoAccount.get("email")); } - // 프로필 - Map profile = (Map) kakaoAccount.get("profile"); - if(null != profile) { - nickName = (String) profile.get("nickname"); - profileImg = (String) profile.get("profile_image_url"); - } - // id에 prefix 추가 id = "provider=kakao" + id; } @@ -98,7 +90,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic dto.setUserId(id); dto.setEmail(email); dto.setNickName(nickName); - dto.setProfileImg(profileImg); dto.setJoinType("KAKAO"); int insertResult = joinService.insertMembershipInfo(dto); @@ -114,7 +105,6 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic unified.put("id", id); unified.put("email", email != null ? email : ""); unified.put("name", nickName != null ? nickName : ""); - unified.put("picture", profileImg != null ? profileImg : ""); return new DefaultOAuth2User( List.of(new SimpleGrantedAuthority("ROLE_USER")), diff --git a/src/main/java/com/barogagi/member/oauth/join/service/NaverOAuth2UserService.java b/src/main/java/com/barogagi/member/oauth/join/service/NaverOAuth2UserService.java index 9f52ad0..55e1175 100644 --- a/src/main/java/com/barogagi/member/oauth/join/service/NaverOAuth2UserService.java +++ b/src/main/java/com/barogagi/member/oauth/join/service/NaverOAuth2UserService.java @@ -62,7 +62,6 @@ public OAuth2User loadUser(OAuth2UserRequest req) { String id = str(resp.get("id")); String nickName = str(resp.get("nickname")); - String profileImg = str(resp.get("profile_image")); String gender = str(resp.get("gender")); String email = str(resp.get("email")); String birthday = str(resp.get("birthday")); @@ -87,12 +86,6 @@ public OAuth2User loadUser(OAuth2UserRequest req) { joinDTO.setNickName(nickName); joinDTO.setJoinType("NAVER"); - logger.info("@@ profileImg={}", null != profileImg); - if(null != profileImg) { - // 프로필 사진 - joinDTO.setProfileImg(profileImg); - } - // gender(성별) : M(남성), F(여성), U(미설정) logger.info("@@ gender={}", null != gender); if(null != gender) { diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java index d26b90b..3f66ab9 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistReqDTO.java @@ -3,6 +3,7 @@ import com.barogagi.region.dto.RegionRegistReqDTO; import com.barogagi.tag.dto.TagRegistReqDTO; import io.swagger.v3.oas.annotations.media.ArraySchema; +import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -12,6 +13,7 @@ @Getter @ToString +@Builder(toBuilder = true) @Schema(description = "계획 등록 요청 DTO") public class PlanRegistReqDTO { @@ -33,6 +35,9 @@ public class PlanRegistReqDTO { @Schema(description = "계획 태그 목록") public List planTagRegistReqDTOList; + @Schema(description = "사용자가 랜덤 카테고리를 선택한 경우", example = "Y") + public String isRandomCategory; + // 사용자가 직접 세부일정을 추가한 경우에만 필요한 값 @Schema(description = "사용자가 수동으로 추가한 일정인지 여부(AI 생성 안함)", example = "Y") public String isUserAdded; diff --git a/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java b/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java index f39d2bb..124ad5f 100644 --- a/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java +++ b/src/main/java/com/barogagi/plan/query/mapper/CategoryMapper.java @@ -10,4 +10,6 @@ public interface CategoryMapper { // 카테고리명 조회 String selectCategoryNmBy(int categoryNum); + + int selectRandomCategoryNum(); } diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index bbd5960..989f08c 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -176,6 +176,14 @@ private PlanRegistResDTO handleAIPlan(ScheduleRegistReqDTO scheduleRegistReqDTO, List> allKakaoPlaceResults = new ArrayList<>(); + // 랜덤 카테고리를 선택한 경우, 카테고리 번호를 랜덤으로 선택 + if (plan.getIsRandomCategory().equals("Y")) { + plan = plan.toBuilder() + .categoryNum(categoryMapper.selectRandomCategoryNum()) + .build(); + logger.info("#$# getCategoryNum={}", plan.getCategoryNum()); + } + String categoryNm = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); String queryString = categoryNm; // 검색어 diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 4f9be73..099ad26 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -104,9 +104,10 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 description = "일정을 생성하는 기능입니다.
" + "- 사용자가 직접 일정을 생성하는 경우는 2가지가 존재합니다.
" + "     CASE 1. 카카오 장소 검색 API를 사용해서 사용자가 가고 싶은 장소를 선택하는 경우, 카카오 장소 검색 API에서 검색한 placeName, placeUrl, addressName을 보내주세요.
" + + "       주의 1) 사용자가 랜덤 카테고리를 선택한 경우 isRandomCategory=\"Y\"로 전달해 주세요. 이때 categoryNum은 전달하지 않아도 됩니다.
" + "     CASE 2. 사용자가 세부일정을 직접 텍스트로 입력하는 경우(ex, 친구집 방문), 세부일정명을 planNm 필드에 담아 보내주세요.
" + - "     주의 1) 반드시 isUserAdded=\"Y\"로 전달해 주세요.
" + - "     주의 2) 사용자가 직접 일정을 생성하는 경우 planTagRegistReqDTOList 값을 전달할 필요는 없습니다.
" + + "       주의 1) 반드시 isUserAdded=\"Y\"로 전달해 주세요.
" + + "       주의 2) 사용자가 직접 일정을 생성하는 경우 planTagRegistReqDTOList 값을 전달할 필요는 없습니다.
" + "- 생성된 일정은 '일정 등록'과정을 거쳐야 DB에 저장됩니다.
" + "- 사용자가 이 API로 생성된 일정을 확인한 후 '일정 생성하기' 버튼을 누르면 '일정 등록' API를 호출해 주세요.") @PostMapping("/create") @@ -134,6 +135,7 @@ public ApiResponse createSchedule( " \"itemNum\": 10,\n" + " \"categoryNum\": 2,\n" + " \"isUserAdded\": \"N\",\n" + + " \"isRandomCategory\": \"Y\",\n" + " \"regionRegistReqDTOList\": [\n" + " { \"regionNum\": 1 }\n" + " ],\n" + @@ -148,6 +150,7 @@ public ApiResponse createSchedule( " \"itemNum\": 2,\n" + " \"categoryNum\": 1,\n" + " \"isUserAdded\": \"Y\",\n" + + " \"isRandomCategory\": \"N\",\n" + " \"userAddedPlaceDTO\": {\n" + " \"placeName\": \"카카오프렌즈 코엑스점\",\n" + " \"placeUrl\": \"http://place.map.kakao.com/26338954\",\n" + diff --git a/src/main/java/com/barogagi/terms/controller/TermsController.java b/src/main/java/com/barogagi/terms/controller/TermsController.java index 92daee9..a839aa3 100644 --- a/src/main/java/com/barogagi/terms/controller/TermsController.java +++ b/src/main/java/com/barogagi/terms/controller/TermsController.java @@ -3,11 +3,8 @@ import com.barogagi.member.login.dto.LoginVO; import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; +import com.barogagi.terms.dto.*; import com.barogagi.terms.service.TermsService; -import com.barogagi.terms.dto.TermsInputDTO; -import com.barogagi.terms.dto.TermsDTO; -import com.barogagi.terms.dto.TermsOutputDTO; -import com.barogagi.terms.dto.TermsProcessDTO; import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -17,6 +14,7 @@ import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; import java.util.List; @Tag(name = "약관", description = "약관 관련 API") @@ -40,7 +38,14 @@ public TermsController(Environment environment, InputValidate inputValidate, this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다. apiSecretKey와 termsType값만 보내주시면 됩니다.") + @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 조회에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "조회하실 약관의 종류 값이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "약관이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/list") public ApiResponse termsList(@RequestBody TermsInputDTO termsInputDTO){ logger.info("CALL /terms/list"); @@ -89,7 +94,14 @@ public ApiResponse termsList(@RequestBody TermsInputDTO termsInputDTO){ return apiResponse; } - @Operation(summary = "약관 동의 여부 저장", description = "약관 동의 여부 저장 기능입니다.") + @Operation(summary = "약관 동의 여부 저장", description = "약관 동의 여부 저장 기능입니다.", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 저장에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "해당 사용자의 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "약관 저장에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/agree/insert") public ApiResponse insertTermsAgree(@RequestBody TermsDTO termsDTO) { logger.info("CALL /agree/insert"); @@ -108,11 +120,20 @@ public ApiResponse insertTermsAgree(@RequestBody TermsDTO termsDTO) { LoginVO loginVO = loginService.findMembershipNo(lvo); if(null != loginVO) { + + List termsAgreeDTOList = new ArrayList<>(); List termsAgreeList = termsDTO.getTermsAgreeList(); + for(TermsProcessDTO termsProcessDTO : termsAgreeList) { - termsProcessDTO.setMembershipNo(loginVO.getMembershipNo()); + + TermsAgreeDTO termsAgreeDTO = new TermsAgreeDTO(); + termsAgreeDTO.setMembershipNo(loginVO.getMembershipNo()); + termsAgreeDTO.setTermsNum(termsProcessDTO.getTermsNum()); + termsAgreeDTO.setAgreeYn(termsProcessDTO.getAgreeYn()); + + termsAgreeDTOList.add(termsAgreeDTO); } - String resCode = termsService.insertTermsAgreeList(termsAgreeList); + String resCode = termsService.insertTermsAgreeList(termsAgreeDTOList); if(resCode.equals("200")) { resultCode = "200"; message = "약관 저장에 성공하였습니다."; diff --git a/src/main/java/com/barogagi/terms/dto/TermsAgreeDTO.java b/src/main/java/com/barogagi/terms/dto/TermsAgreeDTO.java new file mode 100644 index 0000000..1947d9c --- /dev/null +++ b/src/main/java/com/barogagi/terms/dto/TermsAgreeDTO.java @@ -0,0 +1,12 @@ +package com.barogagi.terms.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TermsAgreeDTO { + private String membershipNo = ""; + private int termsNum = 0; + private String agreeYn = ""; +} diff --git a/src/main/java/com/barogagi/terms/dto/TermsDTO.java b/src/main/java/com/barogagi/terms/dto/TermsDTO.java index 37fd45b..e20c5c0 100644 --- a/src/main/java/com/barogagi/terms/dto/TermsDTO.java +++ b/src/main/java/com/barogagi/terms/dto/TermsDTO.java @@ -12,9 +12,7 @@ @Getter @Setter public class TermsDTO extends DefaultVO { - private int termsNum = 0; private String userId = ""; - private String agreeYn = ""; @ArraySchema(schema = @Schema(implementation = TermsProcessDTO.class)) private List termsAgreeList = new ArrayList<>(); diff --git a/src/main/java/com/barogagi/terms/dto/TermsProcessDTO.java b/src/main/java/com/barogagi/terms/dto/TermsProcessDTO.java index 63c2f61..cd6b9fc 100644 --- a/src/main/java/com/barogagi/terms/dto/TermsProcessDTO.java +++ b/src/main/java/com/barogagi/terms/dto/TermsProcessDTO.java @@ -8,7 +8,5 @@ @Setter public class TermsProcessDTO { private int termsNum = 0; - private String userId = ""; private String agreeYn = ""; - private String membershipNo = ""; } diff --git a/src/main/java/com/barogagi/terms/mapper/TermsMapper.java b/src/main/java/com/barogagi/terms/mapper/TermsMapper.java index c12e3d6..2649286 100644 --- a/src/main/java/com/barogagi/terms/mapper/TermsMapper.java +++ b/src/main/java/com/barogagi/terms/mapper/TermsMapper.java @@ -1,5 +1,6 @@ package com.barogagi.terms.mapper; +import com.barogagi.terms.dto.TermsAgreeDTO; import com.barogagi.terms.dto.TermsInputDTO; import com.barogagi.terms.dto.TermsOutputDTO; import com.barogagi.terms.dto.TermsProcessDTO; @@ -12,5 +13,5 @@ public interface TermsMapper { // 사용중인 약관 목록 조회 List selectTermsList(TermsInputDTO termsInputDTO); - int insertTermsAgreeInfo(TermsProcessDTO vo); + int insertTermsAgreeInfo(TermsAgreeDTO vo); } diff --git a/src/main/java/com/barogagi/terms/service/TermsService.java b/src/main/java/com/barogagi/terms/service/TermsService.java index 22c4fbe..6e49b3d 100644 --- a/src/main/java/com/barogagi/terms/service/TermsService.java +++ b/src/main/java/com/barogagi/terms/service/TermsService.java @@ -1,5 +1,6 @@ package com.barogagi.terms.service; +import com.barogagi.terms.dto.TermsAgreeDTO; import com.barogagi.terms.mapper.TermsMapper; import com.barogagi.terms.dto.TermsInputDTO; import com.barogagi.terms.dto.TermsOutputDTO; @@ -27,15 +28,15 @@ public List selectTermsList(TermsInputDTO termsInputDTO) throws } // 약관 동의 여부 저장 - public int insertTermsAgreeInfo(TermsProcessDTO vo) throws Exception { + public int insertTermsAgreeInfo(TermsAgreeDTO vo) throws Exception { return termsMapper.insertTermsAgreeInfo(vo); } @Transactional - public String insertTermsAgreeList(List termsList) { + public String insertTermsAgreeList(List termsList) { String resultCode = ""; try { - for(TermsProcessDTO vo : termsList) { + for(TermsAgreeDTO vo : termsList) { int insertFlag = this.insertTermsAgreeInfo(vo); if(insertFlag > 0){ resultCode = "200"; diff --git a/src/main/resources/mapper/AuthMapper.xml b/src/main/resources/mapper/AuthMapper.xml new file mode 100644 index 0000000..00a2901 --- /dev/null +++ b/src/main/resources/mapper/AuthMapper.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/src/main/resources/mapper/CategoryMapper.xml b/src/main/resources/mapper/CategoryMapper.xml index 7e71e6e..681a544 100644 --- a/src/main/resources/mapper/CategoryMapper.xml +++ b/src/main/resources/mapper/CategoryMapper.xml @@ -16,4 +16,14 @@ ]]> + + +
diff --git a/src/main/resources/mapper/JoinMapper.xml b/src/main/resources/mapper/JoinMapper.xml index be80a85..8acef38 100644 --- a/src/main/resources/mapper/JoinMapper.xml +++ b/src/main/resources/mapper/JoinMapper.xml @@ -3,8 +3,8 @@ diff --git a/src/main/resources/mapper/LoginMapper.xml b/src/main/resources/mapper/LoginMapper.xml index 8a32faf..b00464c 100644 --- a/src/main/resources/mapper/LoginMapper.xml +++ b/src/main/resources/mapper/LoginMapper.xml @@ -1,6 +1,6 @@ - + diff --git a/src/main/resources/mapper/MemberMapper.xml b/src/main/resources/mapper/MemberMapper.xml index a518aa7..e6cd44c 100644 --- a/src/main/resources/mapper/MemberMapper.xml +++ b/src/main/resources/mapper/MemberMapper.xml @@ -10,7 +10,6 @@ BIRTH as birth, TEL as tel, GENDER as gender, - PROFILE_IMG as profileImg, NICKNAME as nickName, JOIN_TYPE as joinType, REG_DATE as regDate, @@ -31,7 +30,6 @@ BIRTH as birth, TEL as tel, GENDER as gender, - PROFILE_IMG as profileImg, NICKNAME as nickName, JOIN_TYPE as joinType, REG_DATE as regDate, @@ -46,13 +44,9 @@ - + Date: Sun, 7 Dec 2025 14:43:19 +0900 Subject: [PATCH 40/79] =?UTF-8?q?[FIX]=20openjdk:17=EC=9D=B4=20deprecated?= =?UTF-8?q?=20=EB=90=98=EC=96=B4=20=EB=8F=84=EC=BB=A4=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20(#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: openjdk:17이 deprecated 되어 도커파일 변경 * chore: PR 템플릿 오타 수정 --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 230993b..900e2b6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -## Chacnged +## Changed > 간단한 설명 (예: 로그인 완료 후 세션 동기화 누락 문제 해결) 수정 내용 - 예) 로그인 성공 시 `store.setSession()` 호출 추가 diff --git a/Dockerfile b/Dockerfile index d88c088..7c4252a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17 +FROM eclipse-temurin:17-jdk WORKDIR /app COPY *.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file From c1026f5cabbbc92315a5cf120d16777965b8fb4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EB=8B=A4=EB=AF=BC?= Date: Sun, 7 Dec 2025 14:44:17 +0900 Subject: [PATCH 41/79] =?UTF-8?q?[INFRA]=20Dockerfile=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20main=EC=9C=BC=EB=A1=9C=20merge=20(#28)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 카카오 일정 조회 구현 중 * fix: import 임시 주석 처리 * feat: 일정 등록시 카카오 API 조회 * feat: 카카오 장소 조회 기능 추가 * feat: 네이버 블로그 조회 기능 추가 * feat: 태그번호로 태그명 조회 기능 * feat: ai 추천 로직 진행중 * feat: ai 일정 추천 기능 진행중 * feat: ai 일정 추천 결과 리턴하도록 작업 * chore: 필요 없는 로그 주석처리 * fix: tag 조회 쿼리 테이블 명 대문자로 수정(소문자 사용 시 에러) * chore: AIClient 필요없는 주석 제거 * feat: 주소를 x,y 좌표로 변환하는 api 추가 * feat: ApiResponse에 success/error 정적팩토리 메서드 추가 * feat: 주소 목록 검색 api 추가 * fix: naverBlog 패키지 이름 수정 * fix: tagList를 int 배열이 아니라 map 형태로 보내주도록 수정 * fix: 일정 생성 기능 수정 * feat: 생성된 일정을 DB에 저장하는 API 작업 * update: 일정 상세 조회 API의 태그 조회 쿼리 수정 * feat: 내 일정 목록 조회 API 작업 * update: Plan, Schedule 엔티티에 등록일, 수정일, 삭제여부 컬럼 추가 * update: Plan, Schedule 저장시 삭제여부 함께 저장, 일정 저장 API 메서드명 수정 * feat: 전체 일정 삭제 API 작업 * feat: 일정 수정, 부분삭제 API 작업 * feat: 카카오 장소 키워드 검색 API 추가 * fix: 일정 수정 시 soft-delete 하도록 변경 * feat: 사용자가 직접 추가한 일정, 카카오 API로 검색한 일정 생성할 수 있도록 수정 * fix: 빌드 에러 해결 * Fix/member api (#21) * FIX 1. 일반 회원가입 관련 API return 값을 swagger에 상세히 작성 2. 회원가입 정보 저장 API 에 아이디 중복 체크 추가 * FIX 1. 약관 정보 등록 API 수정 * FIX 1. 일반 로그인 관련 API 수정 * FIX 1. 회원 정보 수정 API 수정 * FIX 1. oauth, login 관련 코드 수정 * FIX 1. oauth, login 관련 코드 수정 * [FIX] 인기 태그 조회 API 수정 (#22) 1. 인기 태그 조회 API 수정 - 태그가 최대 10개만 조회되도록 수정 * [FIX] 회원 정보 관련 API 수정 (#23) - 프로필 이미지 저장되지 않도록 수정 - 프로필 이미지 조회 불가능하도록 수정 - 회원 정보 수정 시 닉네임, 성별, 생년월일만 수정되도록 수정 * [FIX] 회원 탈퇴 코드 수정 (#24) * [FIX] 회원 탈퇴 코드 수정 - access token이 아닌 refresh token 값으로 회원 탈퇴 처리 * [FIX] 회원 탈퇴 코드 수정 - token 이용해서 db 조회 전에 검증 * feat: 일정 생성 시 랜덤 카테고리를 선택할 수 있도록 수정 (#25) * [FIX] openjdk:17이 deprecated 되어 도커파일 변경 (#27) * fix: openjdk:17이 deprecated 되어 도커파일 변경 * chore: PR 템플릿 오타 수정 --------- Co-authored-by: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 230993b..900e2b6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -## Chacnged +## Changed > 간단한 설명 (예: 로그인 완료 후 세션 동기화 누락 문제 해결) 수정 내용 - 예) 로그인 성공 시 `store.setSession()` 호출 추가 diff --git a/Dockerfile b/Dockerfile index d88c088..7c4252a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17 +FROM eclipse-temurin:17-jdk WORKDIR /app COPY *.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file From 918197e44b7367bca2e10c3274e4fd4adf399923 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EB=8B=A4=EB=AF=BC?= Date: Sat, 13 Dec 2025 11:59:59 +0900 Subject: [PATCH 42/79] =?UTF-8?q?[FEAT]=20=EB=9E=9C=EB=8D=A4=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EC=A7=80=EC=A0=95=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95,=20sc?= =?UTF-8?q?heduleTag=20=EC=A0=80=EC=9E=A5=20=EC=95=88=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 일정 생성 시 랜덤 카테고리를 선택할 수 있도록 수정 * fix: scheduleTag 저장 안되는 문제 수정 --- .../ex_entity/PlanUserMembershipInfo.java | 2 +- .../schedule/command/entity/Schedule.java | 6 +-- .../repository/ScheduleRepository.java | 4 +- .../service/ScheduleCommandService.java | 14 ++++--- .../controller/ScheduleController.java | 13 +++--- .../schedule/dto/ScheduleListGroupResDTO.java | 16 ++++++++ .../query/service/ScheduleQueryService.java | 41 +++++++++++++------ .../tag/command/entity/ScheduleTag.java | 2 + 8 files changed, 68 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/barogagi/schedule/dto/ScheduleListGroupResDTO.java diff --git a/src/main/java/com/barogagi/plan/command/ex_entity/PlanUserMembershipInfo.java b/src/main/java/com/barogagi/plan/command/ex_entity/PlanUserMembershipInfo.java index 3608cdb..c040379 100644 --- a/src/main/java/com/barogagi/plan/command/ex_entity/PlanUserMembershipInfo.java +++ b/src/main/java/com/barogagi/plan/command/ex_entity/PlanUserMembershipInfo.java @@ -13,7 +13,7 @@ public class PlanUserMembershipInfo { @Id @Column(name = "MEMBERSHIP_NO") - private Integer membershipNo; // 회원번호 + private String membershipNo; // 회원번호 @Column(name = "USER_ID", nullable = false, length = 100) private String userId; // 아이디 diff --git a/src/main/java/com/barogagi/schedule/command/entity/Schedule.java b/src/main/java/com/barogagi/schedule/command/entity/Schedule.java index 2a26c66..d82a790 100644 --- a/src/main/java/com/barogagi/schedule/command/entity/Schedule.java +++ b/src/main/java/com/barogagi/schedule/command/entity/Schedule.java @@ -24,7 +24,7 @@ public class Schedule { private int scheduleNum; // 일정 번호 (PK) @Column(name = "MEMBERSHIP_NO", nullable = false) - private int membershipNo; // 회원 번호 (FK) + private String membershipNo; // 회원 번호 (FK) @Column(name = "SCHEDULE_NM", nullable = false, length = 100) private String scheduleNm; // 일정명 @@ -35,8 +35,8 @@ public class Schedule { @Column(name = "END_DATE", nullable = false) private String endDate; // 종료 날짜 -// @Column(name = "RADIUS", nullable = false) -// private int radius; // 추천 반경 (미터 단위) + @Column(name = "RADIUS", nullable = false) + private int radius; // 추천 반경 (미터 단위) @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true) private List plans = new ArrayList<>(); diff --git a/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java b/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java index 659b660..0238676 100644 --- a/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java +++ b/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java @@ -7,6 +7,6 @@ import java.util.Optional; @Repository -public interface ScheduleRepository extends JpaRepository{ - Optional findByScheduleNumAndMembershipNo(Integer scheduleNum, Integer membershipNo); +public interface ScheduleRepository extends JpaRepository{ + Optional findByScheduleNumAndMembershipNo(Integer scheduleNum, String membershipNo); } \ No newline at end of file diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 989f08c..4dd4755 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -415,11 +415,11 @@ public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ // 1. Schedule Schedule schedule = Schedule.builder() - .membershipNo(1) // todo. token에서 정보 가져오는 방식으로 수정 필요 + .membershipNo("1") // todo. token에서 정보 가져오는 방식으로 수정 필요 .scheduleNm(scheduleRegistResDTO.getScheduleNm()) .startDate(scheduleRegistResDTO.getStartDate()) .endDate(scheduleRegistResDTO.getEndDate()) - // .radius(radius) + .radius(radius) .delYn("N") .build(); @@ -428,6 +428,8 @@ public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ // 2. Schedule_tag if (scheduleRegistResDTO.getScheduleTagRegistResDTOList() != null) { + logger.info("schedule save result id={}", schedule.getScheduleNum()); + for (TagRegistResDTO tagReq : scheduleRegistResDTO.getScheduleTagRegistResDTOList()) { Tag tag = tagRepository.findById(tagReq.getTagNum()) .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagReq.getTagNum())); @@ -452,7 +454,7 @@ public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ .orElseThrow(() -> new IllegalArgumentException("Item not found: " + planRes.getItemNum())); PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() - .membershipNo(1) // todo. token에서 정보 가져오는 방식으로 수정 필요 + .membershipNo("1") // todo. token에서 정보 가져오는 방식으로 수정 필요 .build(); // 3-1. Plan @@ -545,7 +547,7 @@ public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ @Transactional - public boolean deleteSchedule(Integer scheduleNum, Integer membershipNo) { + public boolean deleteSchedule(Integer scheduleNum, String membershipNo) { Optional optional = scheduleRepository.findByScheduleNumAndMembershipNo(scheduleNum, membershipNo); if (optional.isPresent()) { Schedule schedule = optional.get(); @@ -559,7 +561,7 @@ public boolean deleteSchedule(Integer scheduleNum, Integer membershipNo) { public boolean updateSchedule(ScheduleRegistResDTO dto) { // 1) Schedule 조회 - Schedule schedule = scheduleRepository.findById(dto.getScheduleNum()) + Schedule schedule = scheduleRepository.findById(String.valueOf(dto.getScheduleNum())) .orElseThrow(() -> new IllegalArgumentException("Schedule not found: " + dto.getScheduleNum())); // 2) Schedule 기본 정보 업데이트 @@ -602,7 +604,7 @@ public boolean updateSchedule(ScheduleRegistResDTO dto) { .orElseThrow(() -> new IllegalArgumentException("Item not found")); PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() - .membershipNo(1) + .membershipNo("1") .build(); // 새 Plan 생성 diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 099ad26..87a5922 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -10,10 +10,7 @@ import com.barogagi.region.dto.RegionSearchResDTO; import com.barogagi.response.ApiResponse; import com.barogagi.schedule.command.service.ScheduleCommandService; -import com.barogagi.schedule.dto.ScheduleDetailResDTO; -import com.barogagi.schedule.dto.ScheduleListResDTO; -import com.barogagi.schedule.dto.ScheduleRegistReqDTO; -import com.barogagi.schedule.dto.ScheduleRegistResDTO; +import com.barogagi.schedule.dto.*; import com.barogagi.schedule.query.service.ScheduleQueryService; import com.barogagi.schedule.query.vo.ScheduleDetailVO; import com.barogagi.util.InputValidate; @@ -62,7 +59,7 @@ public ApiResponse getScheduleList() { logger.info("CALL /schedule/list"); - List result; + ScheduleListGroupResDTO result; try { // TODO. token으로 사용자 확인 후, 해당 사용자의 일정만 조회하도록 수정해야 함 //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ @@ -212,6 +209,10 @@ public ApiResponse saveSchedule( " \"scheduleNm\": \"서울 데이트 코스\",\n" + " \"startDate\": \"2025-07-01\",\n" + " \"endDate\": \"2025-07-01\",\n" + + " \"scheduleTagRegistResDTOList\": [\n" + + " { \"tagNm\": \"핫플\", \"tagNum\": 5 },\n" + + " { \"tagNm\": \"활동적인\", \"tagNum\": 8 }\n" + + " ],\n" + " \"planRegistResDTOList\": [\n" + " {\n" + " \"startTime\": \"08:30\",\n" + @@ -398,7 +399,7 @@ public ApiResponse deleteSchedule(@Parameter(description = "삭제할 일정 번 try { //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ // TODO. membershipNo를 토큰으로부터 받아와야 함 - result = scheduleCommandService.deleteSchedule(scheduleNum, 1); + result = scheduleCommandService.deleteSchedule(scheduleNum, "1"); if (!result) return ApiResponse.error("404", "일정 삭제 실패"); diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleListGroupResDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleListGroupResDTO.java new file mode 100644 index 0000000..513f158 --- /dev/null +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleListGroupResDTO.java @@ -0,0 +1,16 @@ +package com.barogagi.schedule.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; +@Getter +@ToString +@Builder(toBuilder = true) +@Schema(description = "과거/미래 일정 목록 조회 DTO") +public class ScheduleListGroupResDTO { + private List pastSchedules; + private List upcomingSchedules; +} diff --git a/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java b/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java index ade46bd..ad817ec 100644 --- a/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java +++ b/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java @@ -3,6 +3,7 @@ import com.barogagi.plan.query.service.PlanQueryService; import com.barogagi.plan.query.vo.PlanDetailVO; import com.barogagi.schedule.dto.ScheduleDetailResDTO; +import com.barogagi.schedule.dto.ScheduleListGroupResDTO; import com.barogagi.schedule.dto.ScheduleListResDTO; import com.barogagi.schedule.query.mapper.ScheduleMapper; import com.barogagi.schedule.query.vo.ScheduleDetailVO; @@ -12,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.LocalDate; import java.util.List; @Service @@ -30,23 +32,38 @@ public ScheduleQueryService (ScheduleMapper scheduleMapper, this.planQueryService = planQueryService; } - public List getScheduleList(int membershipNo) { + public ScheduleListGroupResDTO getScheduleList(int membershipNo) { List scheduleListVOList = scheduleMapper.selectScheduleList(membershipNo); // VO -> DTO 변환 - List scheduleListResDTOList = scheduleListVOList.stream().map(scheduleListVO -> - ScheduleListResDTO.builder() - .scheduleNum(scheduleListVO.getScheduleNum()) - .scheduleNm(scheduleListVO.getScheduleNm()) - .startDate(scheduleListVO.getStartDate()) - .endDate(scheduleListVO.getEndDate()) - .scheduleTagRegistResDTOList(scheduleListVO.getScheduleTagRegistResDTOList()) - .build() - ).toList(); - - return scheduleListResDTOList; + List scheduleListResDTOList = scheduleListVOList.stream() + .map(scheduleListVO -> + ScheduleListResDTO.builder() + .scheduleNum(scheduleListVO.getScheduleNum()) + .scheduleNm(scheduleListVO.getScheduleNm()) + .startDate(scheduleListVO.getStartDate()) + .endDate(scheduleListVO.getEndDate()) + .scheduleTagRegistResDTOList(scheduleListVO.getScheduleTagRegistResDTOList()) + .build() + ).toList(); + + LocalDate today = LocalDate.now(); + + List pastSchedules = scheduleListResDTOList.stream() + .filter(s -> LocalDate.parse(s.getEndDate()).isBefore(today)) + .toList(); + + List upcomingSchedules = scheduleListResDTOList.stream() + .filter(s -> !LocalDate.parse(s.getEndDate()).isBefore(today)) + .toList(); + + return ScheduleListGroupResDTO.builder() + .pastSchedules(pastSchedules) + .upcomingSchedules(upcomingSchedules) + .build(); } + public ScheduleDetailResDTO getScheduleDetail(int scheduleNum) throws Exception{ // 일정 정보 조회 ScheduleDetailVO scheduleDetailVO = scheduleMapper.selectScheduleDetail(scheduleNum); diff --git a/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java index eb2c120..5a832b0 100644 --- a/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java +++ b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java @@ -10,6 +10,7 @@ @AllArgsConstructor @Builder(toBuilder = true) @Table(name = "SCHEDULE_TAG") +@EqualsAndHashCode public class ScheduleTag { @EmbeddedId @@ -24,6 +25,7 @@ public class ScheduleTag { @MapsId("tagNum") @JoinColumn(name = "TAG_NUM") private Tag tag; + } From 7ad081a962259aebcf4858e3d111bbb41b11cc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EB=8B=A4=EB=AF=BC?= Date: Sat, 13 Dec 2025 12:01:07 +0900 Subject: [PATCH 43/79] =?UTF-8?q?[INFRA]=20main=20=EB=B8=8C=EB=9E=9C?= =?UTF-8?q?=EC=B9=98=EB=A1=9C=20merge=20(#30)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 카카오 일정 조회 구현 중 * fix: import 임시 주석 처리 * feat: 일정 등록시 카카오 API 조회 * feat: 카카오 장소 조회 기능 추가 * feat: 네이버 블로그 조회 기능 추가 * feat: 태그번호로 태그명 조회 기능 * feat: ai 추천 로직 진행중 * feat: ai 일정 추천 기능 진행중 * feat: ai 일정 추천 결과 리턴하도록 작업 * chore: 필요 없는 로그 주석처리 * fix: tag 조회 쿼리 테이블 명 대문자로 수정(소문자 사용 시 에러) * chore: AIClient 필요없는 주석 제거 * feat: 주소를 x,y 좌표로 변환하는 api 추가 * feat: ApiResponse에 success/error 정적팩토리 메서드 추가 * feat: 주소 목록 검색 api 추가 * fix: naverBlog 패키지 이름 수정 * fix: tagList를 int 배열이 아니라 map 형태로 보내주도록 수정 * fix: 일정 생성 기능 수정 * feat: 생성된 일정을 DB에 저장하는 API 작업 * update: 일정 상세 조회 API의 태그 조회 쿼리 수정 * feat: 내 일정 목록 조회 API 작업 * update: Plan, Schedule 엔티티에 등록일, 수정일, 삭제여부 컬럼 추가 * update: Plan, Schedule 저장시 삭제여부 함께 저장, 일정 저장 API 메서드명 수정 * feat: 전체 일정 삭제 API 작업 * feat: 일정 수정, 부분삭제 API 작업 * feat: 카카오 장소 키워드 검색 API 추가 * fix: 일정 수정 시 soft-delete 하도록 변경 * feat: 사용자가 직접 추가한 일정, 카카오 API로 검색한 일정 생성할 수 있도록 수정 * fix: 빌드 에러 해결 * Fix/member api (#21) * FIX 1. 일반 회원가입 관련 API return 값을 swagger에 상세히 작성 2. 회원가입 정보 저장 API 에 아이디 중복 체크 추가 * FIX 1. 약관 정보 등록 API 수정 * FIX 1. 일반 로그인 관련 API 수정 * FIX 1. 회원 정보 수정 API 수정 * FIX 1. oauth, login 관련 코드 수정 * FIX 1. oauth, login 관련 코드 수정 * [FIX] 인기 태그 조회 API 수정 (#22) 1. 인기 태그 조회 API 수정 - 태그가 최대 10개만 조회되도록 수정 * [FIX] 회원 정보 관련 API 수정 (#23) - 프로필 이미지 저장되지 않도록 수정 - 프로필 이미지 조회 불가능하도록 수정 - 회원 정보 수정 시 닉네임, 성별, 생년월일만 수정되도록 수정 * [FIX] 회원 탈퇴 코드 수정 (#24) * [FIX] 회원 탈퇴 코드 수정 - access token이 아닌 refresh token 값으로 회원 탈퇴 처리 * [FIX] 회원 탈퇴 코드 수정 - token 이용해서 db 조회 전에 검증 * feat: 일정 생성 시 랜덤 카테고리를 선택할 수 있도록 수정 (#25) * [FIX] openjdk:17이 deprecated 되어 도커파일 변경 (#27) * fix: openjdk:17이 deprecated 되어 도커파일 변경 * chore: PR 템플릿 오타 수정 * [FEAT] 랜덤 카테고리 지정 가능하도록 수정, scheduleTag 저장 안되는 문제 수정 (#29) * feat: 일정 생성 시 랜덤 카테고리를 선택할 수 있도록 수정 * fix: scheduleTag 저장 안되는 문제 수정 --------- Co-authored-by: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> --- .../ex_entity/PlanUserMembershipInfo.java | 2 +- .../schedule/command/entity/Schedule.java | 6 +-- .../repository/ScheduleRepository.java | 4 +- .../service/ScheduleCommandService.java | 14 ++++--- .../controller/ScheduleController.java | 13 +++--- .../schedule/dto/ScheduleListGroupResDTO.java | 16 ++++++++ .../query/service/ScheduleQueryService.java | 41 +++++++++++++------ .../tag/command/entity/ScheduleTag.java | 2 + 8 files changed, 68 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/barogagi/schedule/dto/ScheduleListGroupResDTO.java diff --git a/src/main/java/com/barogagi/plan/command/ex_entity/PlanUserMembershipInfo.java b/src/main/java/com/barogagi/plan/command/ex_entity/PlanUserMembershipInfo.java index 3608cdb..c040379 100644 --- a/src/main/java/com/barogagi/plan/command/ex_entity/PlanUserMembershipInfo.java +++ b/src/main/java/com/barogagi/plan/command/ex_entity/PlanUserMembershipInfo.java @@ -13,7 +13,7 @@ public class PlanUserMembershipInfo { @Id @Column(name = "MEMBERSHIP_NO") - private Integer membershipNo; // 회원번호 + private String membershipNo; // 회원번호 @Column(name = "USER_ID", nullable = false, length = 100) private String userId; // 아이디 diff --git a/src/main/java/com/barogagi/schedule/command/entity/Schedule.java b/src/main/java/com/barogagi/schedule/command/entity/Schedule.java index 2a26c66..d82a790 100644 --- a/src/main/java/com/barogagi/schedule/command/entity/Schedule.java +++ b/src/main/java/com/barogagi/schedule/command/entity/Schedule.java @@ -24,7 +24,7 @@ public class Schedule { private int scheduleNum; // 일정 번호 (PK) @Column(name = "MEMBERSHIP_NO", nullable = false) - private int membershipNo; // 회원 번호 (FK) + private String membershipNo; // 회원 번호 (FK) @Column(name = "SCHEDULE_NM", nullable = false, length = 100) private String scheduleNm; // 일정명 @@ -35,8 +35,8 @@ public class Schedule { @Column(name = "END_DATE", nullable = false) private String endDate; // 종료 날짜 -// @Column(name = "RADIUS", nullable = false) -// private int radius; // 추천 반경 (미터 단위) + @Column(name = "RADIUS", nullable = false) + private int radius; // 추천 반경 (미터 단위) @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true) private List plans = new ArrayList<>(); diff --git a/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java b/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java index 659b660..0238676 100644 --- a/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java +++ b/src/main/java/com/barogagi/schedule/command/repository/ScheduleRepository.java @@ -7,6 +7,6 @@ import java.util.Optional; @Repository -public interface ScheduleRepository extends JpaRepository{ - Optional findByScheduleNumAndMembershipNo(Integer scheduleNum, Integer membershipNo); +public interface ScheduleRepository extends JpaRepository{ + Optional findByScheduleNumAndMembershipNo(Integer scheduleNum, String membershipNo); } \ No newline at end of file diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 989f08c..4dd4755 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -415,11 +415,11 @@ public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ // 1. Schedule Schedule schedule = Schedule.builder() - .membershipNo(1) // todo. token에서 정보 가져오는 방식으로 수정 필요 + .membershipNo("1") // todo. token에서 정보 가져오는 방식으로 수정 필요 .scheduleNm(scheduleRegistResDTO.getScheduleNm()) .startDate(scheduleRegistResDTO.getStartDate()) .endDate(scheduleRegistResDTO.getEndDate()) - // .radius(radius) + .radius(radius) .delYn("N") .build(); @@ -428,6 +428,8 @@ public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ // 2. Schedule_tag if (scheduleRegistResDTO.getScheduleTagRegistResDTOList() != null) { + logger.info("schedule save result id={}", schedule.getScheduleNum()); + for (TagRegistResDTO tagReq : scheduleRegistResDTO.getScheduleTagRegistResDTOList()) { Tag tag = tagRepository.findById(tagReq.getTagNum()) .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagReq.getTagNum())); @@ -452,7 +454,7 @@ public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ .orElseThrow(() -> new IllegalArgumentException("Item not found: " + planRes.getItemNum())); PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() - .membershipNo(1) // todo. token에서 정보 가져오는 방식으로 수정 필요 + .membershipNo("1") // todo. token에서 정보 가져오는 방식으로 수정 필요 .build(); // 3-1. Plan @@ -545,7 +547,7 @@ public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ @Transactional - public boolean deleteSchedule(Integer scheduleNum, Integer membershipNo) { + public boolean deleteSchedule(Integer scheduleNum, String membershipNo) { Optional optional = scheduleRepository.findByScheduleNumAndMembershipNo(scheduleNum, membershipNo); if (optional.isPresent()) { Schedule schedule = optional.get(); @@ -559,7 +561,7 @@ public boolean deleteSchedule(Integer scheduleNum, Integer membershipNo) { public boolean updateSchedule(ScheduleRegistResDTO dto) { // 1) Schedule 조회 - Schedule schedule = scheduleRepository.findById(dto.getScheduleNum()) + Schedule schedule = scheduleRepository.findById(String.valueOf(dto.getScheduleNum())) .orElseThrow(() -> new IllegalArgumentException("Schedule not found: " + dto.getScheduleNum())); // 2) Schedule 기본 정보 업데이트 @@ -602,7 +604,7 @@ public boolean updateSchedule(ScheduleRegistResDTO dto) { .orElseThrow(() -> new IllegalArgumentException("Item not found")); PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() - .membershipNo(1) + .membershipNo("1") .build(); // 새 Plan 생성 diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 099ad26..87a5922 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -10,10 +10,7 @@ import com.barogagi.region.dto.RegionSearchResDTO; import com.barogagi.response.ApiResponse; import com.barogagi.schedule.command.service.ScheduleCommandService; -import com.barogagi.schedule.dto.ScheduleDetailResDTO; -import com.barogagi.schedule.dto.ScheduleListResDTO; -import com.barogagi.schedule.dto.ScheduleRegistReqDTO; -import com.barogagi.schedule.dto.ScheduleRegistResDTO; +import com.barogagi.schedule.dto.*; import com.barogagi.schedule.query.service.ScheduleQueryService; import com.barogagi.schedule.query.vo.ScheduleDetailVO; import com.barogagi.util.InputValidate; @@ -62,7 +59,7 @@ public ApiResponse getScheduleList() { logger.info("CALL /schedule/list"); - List result; + ScheduleListGroupResDTO result; try { // TODO. token으로 사용자 확인 후, 해당 사용자의 일정만 조회하도록 수정해야 함 //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ @@ -212,6 +209,10 @@ public ApiResponse saveSchedule( " \"scheduleNm\": \"서울 데이트 코스\",\n" + " \"startDate\": \"2025-07-01\",\n" + " \"endDate\": \"2025-07-01\",\n" + + " \"scheduleTagRegistResDTOList\": [\n" + + " { \"tagNm\": \"핫플\", \"tagNum\": 5 },\n" + + " { \"tagNm\": \"활동적인\", \"tagNum\": 8 }\n" + + " ],\n" + " \"planRegistResDTOList\": [\n" + " {\n" + " \"startTime\": \"08:30\",\n" + @@ -398,7 +399,7 @@ public ApiResponse deleteSchedule(@Parameter(description = "삭제할 일정 번 try { //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ // TODO. membershipNo를 토큰으로부터 받아와야 함 - result = scheduleCommandService.deleteSchedule(scheduleNum, 1); + result = scheduleCommandService.deleteSchedule(scheduleNum, "1"); if (!result) return ApiResponse.error("404", "일정 삭제 실패"); diff --git a/src/main/java/com/barogagi/schedule/dto/ScheduleListGroupResDTO.java b/src/main/java/com/barogagi/schedule/dto/ScheduleListGroupResDTO.java new file mode 100644 index 0000000..513f158 --- /dev/null +++ b/src/main/java/com/barogagi/schedule/dto/ScheduleListGroupResDTO.java @@ -0,0 +1,16 @@ +package com.barogagi.schedule.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; +@Getter +@ToString +@Builder(toBuilder = true) +@Schema(description = "과거/미래 일정 목록 조회 DTO") +public class ScheduleListGroupResDTO { + private List pastSchedules; + private List upcomingSchedules; +} diff --git a/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java b/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java index ade46bd..ad817ec 100644 --- a/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java +++ b/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java @@ -3,6 +3,7 @@ import com.barogagi.plan.query.service.PlanQueryService; import com.barogagi.plan.query.vo.PlanDetailVO; import com.barogagi.schedule.dto.ScheduleDetailResDTO; +import com.barogagi.schedule.dto.ScheduleListGroupResDTO; import com.barogagi.schedule.dto.ScheduleListResDTO; import com.barogagi.schedule.query.mapper.ScheduleMapper; import com.barogagi.schedule.query.vo.ScheduleDetailVO; @@ -12,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.time.LocalDate; import java.util.List; @Service @@ -30,23 +32,38 @@ public ScheduleQueryService (ScheduleMapper scheduleMapper, this.planQueryService = planQueryService; } - public List getScheduleList(int membershipNo) { + public ScheduleListGroupResDTO getScheduleList(int membershipNo) { List scheduleListVOList = scheduleMapper.selectScheduleList(membershipNo); // VO -> DTO 변환 - List scheduleListResDTOList = scheduleListVOList.stream().map(scheduleListVO -> - ScheduleListResDTO.builder() - .scheduleNum(scheduleListVO.getScheduleNum()) - .scheduleNm(scheduleListVO.getScheduleNm()) - .startDate(scheduleListVO.getStartDate()) - .endDate(scheduleListVO.getEndDate()) - .scheduleTagRegistResDTOList(scheduleListVO.getScheduleTagRegistResDTOList()) - .build() - ).toList(); - - return scheduleListResDTOList; + List scheduleListResDTOList = scheduleListVOList.stream() + .map(scheduleListVO -> + ScheduleListResDTO.builder() + .scheduleNum(scheduleListVO.getScheduleNum()) + .scheduleNm(scheduleListVO.getScheduleNm()) + .startDate(scheduleListVO.getStartDate()) + .endDate(scheduleListVO.getEndDate()) + .scheduleTagRegistResDTOList(scheduleListVO.getScheduleTagRegistResDTOList()) + .build() + ).toList(); + + LocalDate today = LocalDate.now(); + + List pastSchedules = scheduleListResDTOList.stream() + .filter(s -> LocalDate.parse(s.getEndDate()).isBefore(today)) + .toList(); + + List upcomingSchedules = scheduleListResDTOList.stream() + .filter(s -> !LocalDate.parse(s.getEndDate()).isBefore(today)) + .toList(); + + return ScheduleListGroupResDTO.builder() + .pastSchedules(pastSchedules) + .upcomingSchedules(upcomingSchedules) + .build(); } + public ScheduleDetailResDTO getScheduleDetail(int scheduleNum) throws Exception{ // 일정 정보 조회 ScheduleDetailVO scheduleDetailVO = scheduleMapper.selectScheduleDetail(scheduleNum); diff --git a/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java index eb2c120..5a832b0 100644 --- a/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java +++ b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java @@ -10,6 +10,7 @@ @AllArgsConstructor @Builder(toBuilder = true) @Table(name = "SCHEDULE_TAG") +@EqualsAndHashCode public class ScheduleTag { @EmbeddedId @@ -24,6 +25,7 @@ public class ScheduleTag { @MapsId("tagNum") @JoinColumn(name = "TAG_NUM") private Tag tag; + } From a17cf204e9336f884945bc4f947132fa07a87320 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:54:25 +0900 Subject: [PATCH 44/79] =?UTF-8?q?[FEAT]=20membershipNo=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EC=BD=94=EB=93=9C=20=EA=B3=B5=ED=86=B5=ED=99=94=20?= =?UTF-8?q?(#31)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] membershipNo 조회 코드 공통화 * [FEAT] exception 수정 * [FEAT] membershipNo 공백 체크 빼기 --- .../controller/MainPageController.java | 18 +++++--- .../info/controller/InfoController.java | 31 ++++++++++---- .../com/barogagi/util/MembershipUtil.java | 42 +++++++++++++++++++ .../util/exception/BasicException.java | 14 +++++++ 4 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/barogagi/util/MembershipUtil.java create mode 100644 src/main/java/com/barogagi/util/exception/BasicException.java diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index 93dfd66..1b5b784 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -6,6 +6,7 @@ import com.barogagi.mainPage.response.MainPageResponse; import com.barogagi.mainPage.service.MainPageService; import com.barogagi.response.ApiResponse; +import com.barogagi.util.MembershipUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.Map; @Tag(name = "메인 화면", description = "메인 화면에 필요한 API") @RestController @@ -29,12 +31,16 @@ public class MainPageController { private final MainPageService mainPageService; + private final MembershipUtil membershipUtil; + private final String API_SECRET_KEY; @Autowired public MainPageController(MainPageService mainPageService, + MembershipUtil membershipUtil, Environment environment) { this.mainPageService = mainPageService; + this.membershipUtil = membershipUtil; this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } @@ -56,12 +62,14 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { try { - // 회원번호 - Object membershipNoAttr = request.getAttribute("membershipNo"); - if (membershipNoAttr == null) { - throw new MainPageException("401", "접근 권한이 존재하지 않습니다."); + // 회원번호 구하기 + Map membershipNoInfo = membershipUtil.MembershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MainPageException(String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message"))); } - String membershipNo = String.valueOf(membershipNoAttr); + + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); // 유저 일정 정보 API UserInfoRequestDTO userInfoRequestDTO = new UserInfoRequestDTO(); diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index f15660e..66e8824 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -1,5 +1,6 @@ package com.barogagi.member.info.controller; +import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.info.exception.MemberInfoException; @@ -8,6 +9,7 @@ import com.barogagi.member.info.service.MemberService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; +import com.barogagi.util.MembershipUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -18,6 +20,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Map; + @Tag(name = "회원 정보", description = "회원 정보 관련 API") @RestController @RequestMapping("/info") @@ -29,14 +33,17 @@ public class InfoController { private final JoinService joinService; private final EncryptUtil encryptUtil; + private final MembershipUtil membershipUtil; public InfoController(MemberService memberService, JoinService joinService, - EncryptUtil encryptUtil) { + EncryptUtil encryptUtil, + MembershipUtil membershipUtil) { this.memberService = memberService; this.joinService = joinService; this.encryptUtil = encryptUtil; + this.membershipUtil = membershipUtil; } @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.", @@ -56,12 +63,14 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { try { - Object membershipNoAttr = request.getAttribute("membershipNo"); - if(membershipNoAttr == null) { - throw new MemberInfoException("401", "접근 권한이 존재하지 않습니다."); + // 회원번호 구하기 + Map membershipNoInfo = membershipUtil.MembershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message"))); } - String membershipNo = String.valueOf(membershipNoAttr); + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); // 회원 정보 조회 Member memberInfo = memberService.findByMembershipNo(membershipNo); @@ -120,12 +129,16 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem logger.info("param gender={}", memberRequestDto.getGender()); logger.info("param nickName={}", memberRequestDto.getNickName()); - String membershipNo = String.valueOf(request.getAttribute("membershipNo")); - logger.info("@@ membershipNo.isEmpty()={}", membershipNo.isEmpty()); - if (membershipNo.isEmpty()) { - throw new MemberInfoException("401", "접근 권한이 존재하지 않습니다."); + // 회원번호 구하기 + Map membershipNoInfo = membershipUtil.MembershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message"))); } + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); + logger.info("@@ membershipNo={}", membershipNo); + // 회원 정보 조회 Member memberInfo = memberService.findByMembershipNo(membershipNo); if(null == memberInfo) { diff --git a/src/main/java/com/barogagi/util/MembershipUtil.java b/src/main/java/com/barogagi/util/MembershipUtil.java new file mode 100644 index 0000000..ab18258 --- /dev/null +++ b/src/main/java/com/barogagi/util/MembershipUtil.java @@ -0,0 +1,42 @@ +package com.barogagi.util; + +import com.barogagi.util.exception.BasicException; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class MembershipUtil { + + public Map membershipNoService(HttpServletRequest request) { + + Map resultMap = new HashMap<>(); + + String resultCode = ""; + String message = ""; + String membershipNo = ""; + + try { + Object membershipNoAttr = request.getAttribute("membershipNo"); + if(membershipNoAttr == null) { + throw new BasicException("401", "접근 권한이 존재하지 않습니다."); + } + + membershipNo = String.valueOf(membershipNoAttr); + resultCode = "200"; + message = "회원 번호가 존재합니다."; + + } catch (BasicException ex) { + resultCode = ex.getResultCode(); + message = ex.getMessage(); + } finally { + resultMap.put("resultCode", resultCode); + resultMap.put("message", message); + resultMap.put("membershipNo", membershipNo); + } + + return resultMap; + } +} diff --git a/src/main/java/com/barogagi/util/exception/BasicException.java b/src/main/java/com/barogagi/util/exception/BasicException.java new file mode 100644 index 0000000..86bf3da --- /dev/null +++ b/src/main/java/com/barogagi/util/exception/BasicException.java @@ -0,0 +1,14 @@ +package com.barogagi.util.exception; + +import lombok.Getter; + +@Getter +public class BasicException extends RuntimeException { + + private final String resultCode; + + public BasicException(String resultCode, String message) { + super(message); + this.resultCode = resultCode; + } +} From 1213c746ff1bccb3598bf9dce129cde09886379a Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:00:00 +0900 Subject: [PATCH 45/79] =?UTF-8?q?[FEAT]=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/barogagi/mainPage/controller/MainPageController.java | 2 +- .../com/barogagi/member/info/controller/InfoController.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index 1b5b784..702cce9 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -63,7 +63,7 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { try { // 회원번호 구하기 - Map membershipNoInfo = membershipUtil.MembershipNoService(request); + Map membershipNoInfo = membershipUtil.membershipNoService(request); if(!membershipNoInfo.get("resultCode").equals("200")) { throw new MainPageException(String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message"))); diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index 66e8824..ad2ae54 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -64,7 +64,7 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { try { // 회원번호 구하기 - Map membershipNoInfo = membershipUtil.MembershipNoService(request); + Map membershipNoInfo = membershipUtil.membershipNoService(request); if(!membershipNoInfo.get("resultCode").equals("200")) { throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message"))); @@ -130,7 +130,7 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem logger.info("param nickName={}", memberRequestDto.getNickName()); // 회원번호 구하기 - Map membershipNoInfo = membershipUtil.MembershipNoService(request); + Map membershipNoInfo = membershipUtil.membershipNoService(request); if(!membershipNoInfo.get("resultCode").equals("200")) { throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message"))); From a714a5c991463ec939f84e82524da029ae32f11c Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:43:22 +0900 Subject: [PATCH 46/79] =?UTF-8?q?[STYLE]=20API=20URL=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20&=20=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20(#33)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [STYLE] : 일반 회원가입 api url 수정 * [STYLE] : 일반 로그인 api url 수정 및 코드 수정 * [STYLE] : 약관 관련 api url 수정 및 코드 수정 * [STYLE] : 회원 정보 관련 api url 수정 및 코드 수정 * [STYLE] : 메인화면에 사용되는 api url 수정 및 코드 수정 * [STYLE] : 인증 관련 api url 수정 및 코드 수정 * [STYLE] : 토큰, 로그아웃, 회원탈퇴 api url 수정 및 코드 수정 * [STYLE] : 수정 --- .../controller/ApprovalController.java | 17 ++- .../com/barogagi/config/SecurityConfig.java | 14 +- .../controller/MainPageController.java | 28 ++-- .../basic/join/controller/JoinController.java | 116 ++++++++++++++--- .../member/basic/join/dto/UserIdCheckDTO.java | 17 --- .../info/controller/InfoController.java | 16 +-- .../login/controller/AuthController.java | 120 ------------------ .../login/controller/LoginController.java | 105 +++++++++++---- .../terms/controller/TermsController.java | 28 ++-- 9 files changed, 226 insertions(+), 235 deletions(-) delete mode 100644 src/main/java/com/barogagi/member/basic/join/dto/UserIdCheckDTO.java diff --git a/src/main/java/com/barogagi/approval/controller/ApprovalController.java b/src/main/java/com/barogagi/approval/controller/ApprovalController.java index 5806bee..d440b4e 100644 --- a/src/main/java/com/barogagi/approval/controller/ApprovalController.java +++ b/src/main/java/com/barogagi/approval/controller/ApprovalController.java @@ -20,7 +20,7 @@ @Tag(name = "인증", description = "인증 API") @RestController -@RequestMapping("/approval/tel") +@RequestMapping("/api/v1/verification-codes") public class ApprovalController { private static final Logger logger = LoggerFactory.getLogger(ApprovalController.class); @@ -46,7 +46,7 @@ public ApprovalController(Environment environment){ this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.", + @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.
회원가입 시 사용할 경우 type 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "인증번호를 발송할 전화번호를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증번호 발송에 성공하었습니다."), @@ -55,10 +55,10 @@ public ApprovalController(Environment environment){ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/authCode/send") + @PostMapping("/send") public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { - logger.info("CALL /approval/tel/authCode/send!"); + logger.info("CALL /api/v1/verification-codes/send"); logger.info("[input] API_SECRET_KEY={}", approvalSendVO.getApiSecretKey()); ApprovalVO approvalVO = new ApprovalVO(); @@ -139,7 +139,10 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { return apiResponse; } - @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인", + @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인." + + "
회원가입 시 사용할 경우 type 값을 JOIN-MEMBERSHIP 값으로 넣어주세요." + + "
authCode에는 인증번호를 넣어주세요." + + "
tel에는 전화번호를 넣어주세요.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호 또는 인증번호 값을 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증이 완료되었습니다."), @@ -147,10 +150,10 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/authCode/check") + @PostMapping("/verify") public ApiResponse approvalTelCheck(@RequestBody ApprovalCompleteVO approvalCompleteVO) { - logger.info("CALL /approval/tel/authCode/check"); + logger.info("CALL /api/v1/verification-codes/verify"); logger.info("[input] API_SECRET_KEY={}", approvalCompleteVO.getApiSecretKey()); ApprovalVO approvalVO = new ApprovalVO(); diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index 83b3de6..43f0f5d 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -38,14 +38,12 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, "/webjars/**", "/login/oauth2/**", "/oauth2/**", - "/login/**", - "/membership/join/**", - "/terms/**", - "/main/page/popular/tag/list", - "/main/page/popular/region/list", - "/approval/tel/authCode/send", - "/approval/tel/authCode/check", - "/auth/**" + "/api/v1/auth/**", // ㄲ일반 회원가입 관련 + "/api/v1/users/**", // 로그인 관련 + "/api/v1/terms/**", // 약관 관련 + "/api/v1/home/tags/popular", // 인기 태그 조회 + "/api/v1/home/regions/popular", // 인기 지역 조회 + "/api/v1/verification-codes/**" // 인증번호 관련 }; @Bean diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index 702cce9..ad1a33f 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -1,6 +1,5 @@ package com.barogagi.mainPage.controller; -import com.barogagi.config.vo.DefaultVO; import com.barogagi.mainPage.dto.*; import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.mainPage.response.MainPageResponse; @@ -14,17 +13,14 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; -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.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; @Tag(name = "메인 화면", description = "메인 화면에 필요한 API") @RestController -@RequestMapping("/main/page") +@RequestMapping("/api/v1/home") public class MainPageController { private static final Logger logger = LoggerFactory.getLogger(MainPageController.class); @@ -51,10 +47,10 @@ public MainPageController(MainPageService mainPageService, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/user/schedule/info") + @GetMapping("/me/schedules") public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { - logger.info("CALL /main/page/user/schedule/info"); + logger.info("CALL /api/v1/home/me/schedules"); MainPageResponse mainPageResponse = new MainPageResponse(); String resultCode = ""; @@ -131,10 +127,10 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 태그 목록이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/popular/tag/list") - public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { + @GetMapping("/tags/popular") + public ApiResponse selectPopularTagList(@RequestHeader("API-KEY") String apiSecretKey) { - logger.info("CALL /main/page/popular/tag/list"); + logger.info("CALL /api/v1/home/tags/popular"); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -142,7 +138,7 @@ public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { try { - if(!vo.getApiSecretKey().equals(API_SECRET_KEY)) { + if(!apiSecretKey.equals(API_SECRET_KEY)) { throw new MainPageException("100", "잘못된 접근입니다."); } @@ -182,10 +178,10 @@ public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 지역 목록이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/popular/region/list") - public ApiResponse selectPopularRegionList(@RequestBody DefaultVO vo) { + @GetMapping("/regions/popular") + public ApiResponse selectPopularRegionList(@RequestHeader("API-KEY") String apiSecretKey) { - logger.info("CALL /main/page/popular/region/list"); + logger.info("CALL /api/v1/home/regions/popular"); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -193,7 +189,7 @@ public ApiResponse selectPopularRegionList(@RequestBody DefaultVO vo) { try { - if(!vo.getApiSecretKey().equals(API_SECRET_KEY)) { + if(!apiSecretKey.equals(API_SECRET_KEY)) { throw new MainPageException("100", "잘못된 접근입니다."); } diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index 783cf97..1362c1a 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -4,8 +4,10 @@ import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.basic.join.dto.JoinDTO; -import com.barogagi.member.basic.join.dto.UserIdCheckDTO; import com.barogagi.member.basic.join.dto.JoinRequestDTO; +import com.barogagi.member.login.exception.InvalidRefreshTokenException; +import com.barogagi.member.login.service.AccountService; +import com.barogagi.member.login.service.AuthService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; @@ -18,13 +20,18 @@ import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; +import java.util.Map; +import java.util.Optional; + @Tag(name = "일반 회원가입", description = "일반 회원가입 관련 API") @RestController -@RequestMapping("/membership/join") +@RequestMapping("/api/v1/users") public class JoinController { private static final Logger logger = LoggerFactory.getLogger(JoinController.class); private final JoinService joinService; + private final AccountService accountService; + private final AuthService authService; private final InputValidate inputValidate; private final EncryptUtil encryptUtil; private final Validator validator; @@ -35,12 +42,16 @@ public class JoinController { @Autowired public JoinController(Environment environment, JoinService joinService, + AccountService accountService, + AuthService authService, InputValidate inputValidate, EncryptUtil encryptUtil, Validator validator, PasswordConfig passwordConfig) { this.API_SECRET_KEY = environment.getProperty("api.secret-key"); this.joinService = joinService; + this.accountService = accountService; + this.authService = authService; this.inputValidate = inputValidate; this.encryptUtil = encryptUtil; this.validator = validator; @@ -56,11 +67,11 @@ public JoinController(Environment environment, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "해당 아이디 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/userId/check") - public ApiResponse checkUserId(@RequestBody UserIdCheckDTO userIdCheckDTO) { + @GetMapping("/userid/exists") + public ApiResponse checkUserId(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String userId) { - logger.info("CALL /membership/join/basic/membership/userId/check"); - logger.info("[input] API_SECRET_KEY={}", userIdCheckDTO.getApiSecretKey()); + logger.info("CALL /api/v1/users/userId/exists"); + logger.info("[input] API_SECRET_KEY={}", apiSecretKey); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -68,19 +79,19 @@ public ApiResponse checkUserId(@RequestBody UserIdCheckDTO userIdCheckDTO) { try { - if(userIdCheckDTO.getApiSecretKey().equals(API_SECRET_KEY)){ + if(apiSecretKey.equals(API_SECRET_KEY)){ - if(inputValidate.isEmpty(userIdCheckDTO.getUserId())) { + if(inputValidate.isEmpty(userId)) { resultCode = "101"; message = "아이디를 입력해주세요."; } else{ - if(!validator.isValidId(userIdCheckDTO.getUserId())) { + if(!validator.isValidId(userId)) { resultCode = "102"; message = "적합한 아이디가 아닙니다."; } else { JoinDTO joinDTO = new JoinDTO(); - joinDTO.setUserId(userIdCheckDTO.getUserId()); + joinDTO.setUserId(userId); int checkUserId = joinService.checkUserId(joinDTO); logger.info("@@ checkUserId={}", checkUserId); @@ -123,10 +134,10 @@ public ApiResponse checkUserId(@RequestBody UserIdCheckDTO userIdCheckDTO) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원가입에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/insert") + @PostMapping public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestDTO){ - logger.info("CALL /membership/join/basic/membership/insert"); + logger.info("CALL /api/v1/users"); logger.info("[input] API_SECRET_KEY={}", joinRequestDTO.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); @@ -222,11 +233,12 @@ public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestD @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "이미 존재하는 닉네임입니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/check/duplicate/nickname") - public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ + @GetMapping("/nickname/exists") + public ApiResponse checkDuplicateNickname(@RequestHeader("API-KEY") String apiSecretKey, + @RequestParam String nickname){ - logger.info("CALL /membership/join/check/duplicate/nickname"); - logger.info("[input] API_SECRET_KEY={}", nickNameDTO.getApiSecretKey()); + logger.info("CALL /api/v1/user/nickname/exists"); + logger.info("[input] API_SECRET_KEY={}", apiSecretKey); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -234,10 +246,10 @@ public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ try { - if(nickNameDTO.getApiSecretKey().equals(API_SECRET_KEY)){ + if(apiSecretKey.equals(API_SECRET_KEY)){ // 필수 입력값 - if(inputValidate.isEmpty(nickNameDTO.getNickName())){ + if(inputValidate.isEmpty(nickname)){ // 필수 입력값 중 빈 값이 존재. insert 중지 resultCode = "101"; @@ -245,10 +257,14 @@ public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ } else{ - if(!validator.isValidNickname(nickNameDTO.getNickName())) { + if(!validator.isValidNickname(nickname)) { resultCode = "102"; message = "적합하지 않는 닉네임입니다."; } else { + + NickNameDTO nickNameDTO = new NickNameDTO(); + nickNameDTO.setNickName(nickname); + int nickNameCnt = joinService.checkNickName(nickNameDTO); logger.info("nickNameCnt={}", nickNameCnt); if(nickNameCnt > 0) { @@ -277,4 +293,66 @@ public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ return apiResponse; } + + @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "refresh token이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) + @DeleteMapping("/me") + public ApiResponse deleteMe(@RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body) { + + logger.info("CALL /api/v1/users/me"); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + String refreshToken = Optional.ofNullable(refreshHeader) + .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) + .orElse(null); + + if (refreshToken == null || refreshToken.isBlank()) { + throw new InvalidRefreshTokenException("100", "refresh token이 존재하지 않습니다."); + } + + // refresh token을 이용해서 membershipNo 구하기 + + Map resultMap = authService.selectUserInfoByToken(refreshToken); + if(!resultMap.get("resultCode").equals("200")) { + throw new InvalidRefreshTokenException(resultMap.get("resultCode"), resultMap.get("message")); + } + + String membershipNo = resultMap.get("membershipNo"); + + int deleteResult = accountService.deleteMyAccount(membershipNo); + if(deleteResult > 0) { + resultCode = "200"; + message = "회원 탈퇴되었습니다."; + } else { + resultCode = "300"; + message = "회원 탈퇴 실패하였습니다."; + } + + } catch (InvalidRefreshTokenException ex) { + resultCode = ex.getCode(); + message = ex.getMessage(); + + } catch (Exception e) { + logger.error("error", e); + resultCode = "400"; + message = "오류가 발생하였습니다."; + + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); + } + + return apiResponse; + } } diff --git a/src/main/java/com/barogagi/member/basic/join/dto/UserIdCheckDTO.java b/src/main/java/com/barogagi/member/basic/join/dto/UserIdCheckDTO.java deleted file mode 100644 index 1491630..0000000 --- a/src/main/java/com/barogagi/member/basic/join/dto/UserIdCheckDTO.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.barogagi.member.basic.join.dto; - -import com.barogagi.config.vo.DefaultVO; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class UserIdCheckDTO extends DefaultVO { - @NotBlank(message = "사용자 ID는 필수 입력값입니다.") - @Size(min = 4, max = 20, message = "사용자 ID는 4자 이상 20자 이하여야 합니다.") - @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "사용자 ID는 영문과 숫자만 사용 가능합니다.") - private String userId = ""; -} diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index ad2ae54..ec586dd 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -1,6 +1,5 @@ package com.barogagi.member.info.controller; -import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.info.exception.MemberInfoException; @@ -15,16 +14,13 @@ import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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.RestController; +import org.springframework.web.bind.annotation.*; import java.util.Map; @Tag(name = "회원 정보", description = "회원 정보 관련 API") @RestController -@RequestMapping("/info") +@RequestMapping("/api/v1/members") public class InfoController { private static final Logger logger = LoggerFactory.getLogger(InfoController.class); @@ -53,9 +49,9 @@ public InfoController(MemberService memberService, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 정보 조회가 완료되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/member") + @GetMapping public ApiResponse selectMemberInfo(HttpServletRequest request) { - logger.info("CALL /info/member"); + logger.info("CALL /api/v1/members"); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -116,9 +112,9 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용자 정보 수정 완료하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/member/update") + @PatchMapping public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody MemberRequestDTO memberRequestDto) { - logger.info("CALL /info/member/update"); + logger.info("CALL /api/v1/members"); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; diff --git a/src/main/java/com/barogagi/member/login/controller/AuthController.java b/src/main/java/com/barogagi/member/login/controller/AuthController.java index 70dafc1..6b73855 100644 --- a/src/main/java/com/barogagi/member/login/controller/AuthController.java +++ b/src/main/java/com/barogagi/member/login/controller/AuthController.java @@ -32,127 +32,7 @@ public AuthController(AuthService authService, this.accountService = accountService; } - @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") - @PostMapping("/refresh") - public ResponseEntity> refresh( - @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body - ) { - logger.info("CALL /auth/refresh"); - - try { - String rt = Optional.ofNullable(refreshHeader) - .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) - .orElse(null); - - if (rt == null || rt.isBlank()) { - return ResponseEntity.status(401).body(Map.of("error", "refresh_required")); - } - - TokenPair pair = authService.rotate(rt); // ❗️핵심 로직 (아래 2) 참조) - - return ResponseEntity.ok(Map.of( - "accessToken", pair.accessToken(), - "accessTokenExpiresIn", pair.accessTokenExpiresIn(), - "refreshToken", pair.refreshToken(), - "refreshTokenExpiresIn", pair.refreshTokenExpiresIn() - )); - - } catch (InvalidRefreshTokenException e) { - return ResponseEntity.status(HttpServletResponse.SC_UNAUTHORIZED) - .body(Map.of( - "resultCode", "400", - "errorCode", e.getCode(), - "message", e.getMessage(), - "needLogin", true - )); - } - } - - /** - * 현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE - * 입력 경로: - * - 헤더: Refresh-Token: - * - 바디: { "refreshToken": "" } - */ - @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") - @PostMapping("/logout") - public ResponseEntity> logout( - @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body - ) { - String refresh = refreshHeader; - if ((refresh == null || refresh.isBlank()) && body != null) { - refresh = body.get("refreshToken"); - } - if (refresh != null && !refresh.isBlank()) { - authService.logout(refresh); // DB REVOKE - } - return ResponseEntity.ok(Map.of("resultCode", "200", "message", "로그아웃 되었습니다.")); - } - - @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", - responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "refresh token이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") - }) - @PostMapping("/member/delete") - public ApiResponse deleteMe(@RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body) { - - logger.info("CALL /auth/member/delete"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - String refreshToken = Optional.ofNullable(refreshHeader) - .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) - .orElse(null); - - if (refreshToken == null || refreshToken.isBlank()) { - throw new InvalidRefreshTokenException("100", "refresh token이 존재하지 않습니다."); - } - - // refresh token을 이용해서 membershipNo 구하기 - - Map resultMap = authService.selectUserInfoByToken(refreshToken); - if(!resultMap.get("resultCode").equals("200")) { - throw new InvalidRefreshTokenException(resultMap.get("resultCode"), resultMap.get("message")); - } - - String membershipNo = resultMap.get("membershipNo"); - - int deleteResult = accountService.deleteMyAccount(membershipNo); - if(deleteResult > 0) { - resultCode = "200"; - message = "회원 탈퇴되었습니다."; - } else { - resultCode = "300"; - message = "회원 탈퇴 실패하였습니다."; - } - - } catch (InvalidRefreshTokenException ex) { - resultCode = ex.getCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; - } } diff --git a/src/main/java/com/barogagi/member/login/controller/LoginController.java b/src/main/java/com/barogagi/member/login/controller/LoginController.java index d00ac21..3e56b77 100644 --- a/src/main/java/com/barogagi/member/login/controller/LoginController.java +++ b/src/main/java/com/barogagi/member/login/controller/LoginController.java @@ -4,41 +4,35 @@ import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.service.MemberService; import com.barogagi.member.login.dto.*; +import com.barogagi.member.login.exception.InvalidRefreshTokenException; import com.barogagi.member.login.exception.LoginException; import com.barogagi.member.login.service.AuthService; import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; -import com.barogagi.util.JwtUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; +import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; -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.RestController; +import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; -@Tag(name = "일반 로그인", description = "일반 로그인 관련 API") +@Tag(name = "일반 로그인 & 토큰 재발급", description = "일반 로그인, 토큰 재발급 관련 API") @RestController -@RequestMapping("/login") +@RequestMapping("/api/v1/auth") public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); private final InputValidate inputValidate; - private final PasswordConfig passwordConfig; private final EncryptUtil encryptUtil; - private final JwtUtil jwtUtil; private final LoginService loginService; private final MemberService memberService; @@ -46,6 +40,8 @@ public class LoginController { private final PasswordEncoder passwordEncoder; + private final PasswordConfig passwordConfig; + private final String API_SECRET_KEY; @Autowired @@ -55,9 +51,8 @@ public LoginController(Environment environment, LoginService loginService, MemberService memberService, AuthService authService, - JwtUtil jwtUtil, - PasswordConfig passwordConfig, - PasswordEncoder passwordEncoder){ + PasswordEncoder passwordEncoder, + PasswordConfig passwordConfig){ this.API_SECRET_KEY = environment.getProperty("api.secret-key"); this.inputValidate = inputValidate; @@ -65,9 +60,8 @@ public LoginController(Environment environment, this.loginService = loginService; this.memberService = memberService; this.authService = authService; - this.jwtUtil = jwtUtil; - this.passwordConfig = passwordConfig; this.passwordEncoder = passwordEncoder; + this.passwordConfig = passwordConfig; } @Operation(summary = "로그인", description = "로그인 기능입니다.", @@ -79,10 +73,10 @@ public LoginController(Environment environment, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/login") + @PostMapping("/login") public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ - logger.info("CALL /login/basic/membership/login"); + logger.info("CALL /api/v1/auth/login"); logger.info("[input] API_SECRET_KEY={}", loginRequestDTO.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); @@ -155,10 +149,10 @@ public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/userId/search") + @PostMapping("/find-user") public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequestDTO){ - logger.info("CALL /login/basic/membership/userId/search"); + logger.info("CALL /api/v1/auth/find-user"); logger.info("[input] API_SECRET_KEY={}", searchUserIdRequestDTO.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); @@ -221,10 +215,10 @@ public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequest @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/password/update") + @PostMapping("/password-reset/confirm") public ApiResponse updatePassword(@RequestBody LoginDTO vo){ - logger.info("CALL /login/basic/membership/password/update"); + logger.info("CALL /api/v1/auth/password-reset/confirm"); logger.info("[input] API_SECRET_KEY={}", vo.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); @@ -240,7 +234,8 @@ public ApiResponse updatePassword(@RequestBody LoginDTO vo){ } else { // 비밀번호 암호화 - vo.setPassword(encryptUtil.hashEncodeString(vo.getPassword())); + String encodedPassword = passwordConfig.passwordEncoder().encode(vo.getPassword()); + vo.setPassword(encodedPassword); // 비밀번호 update int updatePassword = loginService.updatePassword(vo); @@ -270,4 +265,64 @@ public ApiResponse updatePassword(@RequestBody LoginDTO vo){ } return apiResponse; } + + @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") + @PostMapping("/token/refresh") + public ResponseEntity> refresh( + @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body + ) { + + logger.info("CALL /api/v1/auth/token/refresh"); + + try { + String rt = Optional.ofNullable(refreshHeader) + .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) + .orElse(null); + + if (rt == null || rt.isBlank()) { + return ResponseEntity.status(401).body(Map.of("error", "refresh_required")); + } + + TokenPair pair = authService.rotate(rt); // ❗️핵심 로직 (아래 2) 참조) + + return ResponseEntity.ok(Map.of( + "accessToken", pair.accessToken(), + "accessTokenExpiresIn", pair.accessTokenExpiresIn(), + "refreshToken", pair.refreshToken(), + "refreshTokenExpiresIn", pair.refreshTokenExpiresIn() + )); + + } catch (InvalidRefreshTokenException e) { + return ResponseEntity.status(HttpServletResponse.SC_UNAUTHORIZED) + .body(Map.of( + "resultCode", "400", + "errorCode", e.getCode(), + "message", e.getMessage(), + "needLogin", true + )); + } + } + + /** + * 현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE + * 입력 경로: + * - 헤더: Refresh-Token: + * - 바디: { "refreshToken": "" } + */ + @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") + @PostMapping("/logout") + public ResponseEntity> logout( + @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body + ) { + String refresh = refreshHeader; + if ((refresh == null || refresh.isBlank()) && body != null) { + refresh = body.get("refreshToken"); + } + if (refresh != null && !refresh.isBlank()) { + authService.logout(refresh); // DB REVOKE + } + return ResponseEntity.ok(Map.of("resultCode", "200", "message", "로그아웃 되었습니다.")); + } } diff --git a/src/main/java/com/barogagi/terms/controller/TermsController.java b/src/main/java/com/barogagi/terms/controller/TermsController.java index a839aa3..f726d43 100644 --- a/src/main/java/com/barogagi/terms/controller/TermsController.java +++ b/src/main/java/com/barogagi/terms/controller/TermsController.java @@ -19,13 +19,13 @@ @Tag(name = "약관", description = "약관 관련 API") @RestController -@RequestMapping("/terms") +@RequestMapping("/api/v1/terms") public class TermsController { private static final Logger logger = LoggerFactory.getLogger(TermsController.class); - private InputValidate inputValidate; - private TermsService termsService; - private LoginService loginService; + private final InputValidate inputValidate; + private final TermsService termsService; + private final LoginService loginService; private final String API_SECRET_KEY; @@ -38,7 +38,7 @@ public TermsController(Environment environment, InputValidate inputValidate, this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.", + @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.
회원가입 시 사용할 경우 termsType 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 조회에 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @@ -46,22 +46,24 @@ public TermsController(Environment environment, InputValidate inputValidate, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "약관이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/list") - public ApiResponse termsList(@RequestBody TermsInputDTO termsInputDTO){ - logger.info("CALL /terms/list"); - logger.info("[input] API_SECRET_KEY={}", termsInputDTO.getApiSecretKey()); + @GetMapping + public ApiResponse termsList(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String termsType){ + logger.info("CALL /api/v1/terms"); + logger.info("[input] API_SECRET_KEY={}", apiSecretKey); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; String message = ""; try { - if(termsInputDTO.getApiSecretKey().equals(API_SECRET_KEY)) { + if(apiSecretKey.equals(API_SECRET_KEY)) { - if(inputValidate.isEmpty(termsInputDTO.getTermsType())) { + if(inputValidate.isEmpty(termsType)) { resultCode = "101"; message = "조회하실 약관의 종류 값이 존재하지 않습니다."; } else { + TermsInputDTO termsInputDTO = new TermsInputDTO(); + termsInputDTO.setTermsType(termsType); List termsList = termsService.selectTermsList(termsInputDTO); int termsCnt = termsList.size(); @@ -102,9 +104,9 @@ public ApiResponse termsList(@RequestBody TermsInputDTO termsInputDTO){ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "약관 저장에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/agree/insert") + @PostMapping("/terms-agreements") public ApiResponse insertTermsAgree(@RequestBody TermsDTO termsDTO) { - logger.info("CALL /agree/insert"); + logger.info("CALL /api/v1/terms/{userId}/terms-agreements"); logger.info("[input] API_SECRET_KEY={}", termsDTO.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); From 713bf0c1bab07e33af143a12ad6576e788a882d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EB=8B=A4=EB=AF=BC?= Date: Fri, 19 Dec 2025 22:05:29 +0900 Subject: [PATCH 47/79] =?UTF-8?q?[INFRA]=20main=20=EB=B8=8C=EB=9E=9C?= =?UTF-8?q?=EC=B9=98=EB=A1=9C=20merge=20(#34)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 카카오 일정 조회 구현 중 * fix: import 임시 주석 처리 * feat: 일정 등록시 카카오 API 조회 * feat: 카카오 장소 조회 기능 추가 * feat: 네이버 블로그 조회 기능 추가 * feat: 태그번호로 태그명 조회 기능 * feat: ai 추천 로직 진행중 * feat: ai 일정 추천 기능 진행중 * feat: ai 일정 추천 결과 리턴하도록 작업 * chore: 필요 없는 로그 주석처리 * fix: tag 조회 쿼리 테이블 명 대문자로 수정(소문자 사용 시 에러) * chore: AIClient 필요없는 주석 제거 * feat: 주소를 x,y 좌표로 변환하는 api 추가 * feat: ApiResponse에 success/error 정적팩토리 메서드 추가 * feat: 주소 목록 검색 api 추가 * fix: naverBlog 패키지 이름 수정 * fix: tagList를 int 배열이 아니라 map 형태로 보내주도록 수정 * fix: 일정 생성 기능 수정 * feat: 생성된 일정을 DB에 저장하는 API 작업 * update: 일정 상세 조회 API의 태그 조회 쿼리 수정 * feat: 내 일정 목록 조회 API 작업 * update: Plan, Schedule 엔티티에 등록일, 수정일, 삭제여부 컬럼 추가 * update: Plan, Schedule 저장시 삭제여부 함께 저장, 일정 저장 API 메서드명 수정 * feat: 전체 일정 삭제 API 작업 * feat: 일정 수정, 부분삭제 API 작업 * feat: 카카오 장소 키워드 검색 API 추가 * fix: 일정 수정 시 soft-delete 하도록 변경 * feat: 사용자가 직접 추가한 일정, 카카오 API로 검색한 일정 생성할 수 있도록 수정 * fix: 빌드 에러 해결 * Fix/member api (#21) * FIX 1. 일반 회원가입 관련 API return 값을 swagger에 상세히 작성 2. 회원가입 정보 저장 API 에 아이디 중복 체크 추가 * FIX 1. 약관 정보 등록 API 수정 * FIX 1. 일반 로그인 관련 API 수정 * FIX 1. 회원 정보 수정 API 수정 * FIX 1. oauth, login 관련 코드 수정 * FIX 1. oauth, login 관련 코드 수정 * [FIX] 인기 태그 조회 API 수정 (#22) 1. 인기 태그 조회 API 수정 - 태그가 최대 10개만 조회되도록 수정 * [FIX] 회원 정보 관련 API 수정 (#23) - 프로필 이미지 저장되지 않도록 수정 - 프로필 이미지 조회 불가능하도록 수정 - 회원 정보 수정 시 닉네임, 성별, 생년월일만 수정되도록 수정 * [FIX] 회원 탈퇴 코드 수정 (#24) * [FIX] 회원 탈퇴 코드 수정 - access token이 아닌 refresh token 값으로 회원 탈퇴 처리 * [FIX] 회원 탈퇴 코드 수정 - token 이용해서 db 조회 전에 검증 * feat: 일정 생성 시 랜덤 카테고리를 선택할 수 있도록 수정 (#25) * [FIX] openjdk:17이 deprecated 되어 도커파일 변경 (#27) * fix: openjdk:17이 deprecated 되어 도커파일 변경 * chore: PR 템플릿 오타 수정 * [FEAT] 랜덤 카테고리 지정 가능하도록 수정, scheduleTag 저장 안되는 문제 수정 (#29) * feat: 일정 생성 시 랜덤 카테고리를 선택할 수 있도록 수정 * fix: scheduleTag 저장 안되는 문제 수정 * [FEAT] membershipNo 조회 코드 공통화 (#31) * [FEAT] membershipNo 조회 코드 공통화 * [FEAT] exception 수정 * [FEAT] membershipNo 공백 체크 빼기 * [FEAT] 서비스명 수정 (#32) * [STYLE] API URL 수정 & 코드 수정 (#33) * [STYLE] : 일반 회원가입 api url 수정 * [STYLE] : 일반 로그인 api url 수정 및 코드 수정 * [STYLE] : 약관 관련 api url 수정 및 코드 수정 * [STYLE] : 회원 정보 관련 api url 수정 및 코드 수정 * [STYLE] : 메인화면에 사용되는 api url 수정 및 코드 수정 * [STYLE] : 인증 관련 api url 수정 및 코드 수정 * [STYLE] : 토큰, 로그아웃, 회원탈퇴 api url 수정 및 코드 수정 * [STYLE] : 수정 --------- Co-authored-by: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> --- .../controller/ApprovalController.java | 17 ++- .../com/barogagi/config/SecurityConfig.java | 13 +- .../controller/MainPageController.java | 47 +++---- .../basic/join/controller/JoinController.java | 117 ++++++++++++++--- .../member/basic/join/dto/UserIdCheckDTO.java | 17 --- .../info/controller/InfoController.java | 45 ++++--- .../login/controller/AuthController.java | 122 ------------------ .../login/controller/LoginController.java | 105 +++++++++++---- .../terms/controller/TermsController.java | 28 ++-- .../com/barogagi/util/MembershipUtil.java | 42 ++++++ .../util/exception/BasicException.java | 14 ++ 11 files changed, 315 insertions(+), 252 deletions(-) delete mode 100644 src/main/java/com/barogagi/member/basic/join/dto/UserIdCheckDTO.java create mode 100644 src/main/java/com/barogagi/util/MembershipUtil.java create mode 100644 src/main/java/com/barogagi/util/exception/BasicException.java diff --git a/src/main/java/com/barogagi/approval/controller/ApprovalController.java b/src/main/java/com/barogagi/approval/controller/ApprovalController.java index 5806bee..d440b4e 100644 --- a/src/main/java/com/barogagi/approval/controller/ApprovalController.java +++ b/src/main/java/com/barogagi/approval/controller/ApprovalController.java @@ -20,7 +20,7 @@ @Tag(name = "인증", description = "인증 API") @RestController -@RequestMapping("/approval/tel") +@RequestMapping("/api/v1/verification-codes") public class ApprovalController { private static final Logger logger = LoggerFactory.getLogger(ApprovalController.class); @@ -46,7 +46,7 @@ public ApprovalController(Environment environment){ this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.", + @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.
회원가입 시 사용할 경우 type 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "인증번호를 발송할 전화번호를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증번호 발송에 성공하었습니다."), @@ -55,10 +55,10 @@ public ApprovalController(Environment environment){ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/authCode/send") + @PostMapping("/send") public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { - logger.info("CALL /approval/tel/authCode/send!"); + logger.info("CALL /api/v1/verification-codes/send"); logger.info("[input] API_SECRET_KEY={}", approvalSendVO.getApiSecretKey()); ApprovalVO approvalVO = new ApprovalVO(); @@ -139,7 +139,10 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { return apiResponse; } - @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인", + @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인." + + "
회원가입 시 사용할 경우 type 값을 JOIN-MEMBERSHIP 값으로 넣어주세요." + + "
authCode에는 인증번호를 넣어주세요." + + "
tel에는 전화번호를 넣어주세요.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호 또는 인증번호 값을 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증이 완료되었습니다."), @@ -147,10 +150,10 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/authCode/check") + @PostMapping("/verify") public ApiResponse approvalTelCheck(@RequestBody ApprovalCompleteVO approvalCompleteVO) { - logger.info("CALL /approval/tel/authCode/check"); + logger.info("CALL /api/v1/verification-codes/verify"); logger.info("[input] API_SECRET_KEY={}", approvalCompleteVO.getApiSecretKey()); ApprovalVO approvalVO = new ApprovalVO(); diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index 83b3de6..95c803e 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -38,14 +38,11 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, "/webjars/**", "/login/oauth2/**", "/oauth2/**", - "/login/**", - "/membership/join/**", - "/terms/**", - "/main/page/popular/tag/list", - "/main/page/popular/region/list", - "/approval/tel/authCode/send", - "/approval/tel/authCode/check", - "/auth/**" + "/api/v1/auth/**", // ㄲ일반 회원가입 관련 + "/api/v1/users/**", // 로그인 관련 + "/api/v1/terms/**", // 약관 관련 + "/api/v1/home/tags/popular", // 인기 태그 조회 + "/api/v1/home/regions/popular", // 인기 지역 조회 }; @Bean diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index 93dfd66..c7919f1 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -1,11 +1,11 @@ package com.barogagi.mainPage.controller; -import com.barogagi.config.vo.DefaultVO; import com.barogagi.mainPage.dto.*; import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.mainPage.response.MainPageResponse; import com.barogagi.mainPage.service.MainPageService; import com.barogagi.response.ApiResponse; +import com.barogagi.util.MembershipUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -13,28 +13,30 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; -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.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; @Tag(name = "메인 화면", description = "메인 화면에 필요한 API") @RestController -@RequestMapping("/main/page") +@RequestMapping("/api/v1/home") public class MainPageController { private static final Logger logger = LoggerFactory.getLogger(MainPageController.class); private final MainPageService mainPageService; + private final MembershipUtil membershipUtil; + private final String API_SECRET_KEY; @Autowired public MainPageController(MainPageService mainPageService, + MembershipUtil membershipUtil, Environment environment) { this.mainPageService = mainPageService; + this.membershipUtil = membershipUtil; this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } @@ -45,10 +47,10 @@ public MainPageController(MainPageService mainPageService, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/user/schedule/info") + @GetMapping("/me/schedules") public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { - logger.info("CALL /main/page/user/schedule/info"); + logger.info("CALL /api/v1/home/me/schedules"); MainPageResponse mainPageResponse = new MainPageResponse(); String resultCode = ""; @@ -56,12 +58,14 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { try { - // 회원번호 - Object membershipNoAttr = request.getAttribute("membershipNo"); - if (membershipNoAttr == null) { - throw new MainPageException("401", "접근 권한이 존재하지 않습니다."); + // 회원번호 구하기 + Map membershipNoInfo = membershipUtil.membershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MainPageException(String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message"))); } - String membershipNo = String.valueOf(membershipNoAttr); + + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); // 유저 일정 정보 API UserInfoRequestDTO userInfoRequestDTO = new UserInfoRequestDTO(); @@ -123,10 +127,10 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 태그 목록이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/popular/tag/list") - public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { + @GetMapping("/tags/popular") + public ApiResponse selectPopularTagList(@RequestHeader("API-KEY") String apiSecretKey) { - logger.info("CALL /main/page/popular/tag/list"); + logger.info("CALL /api/v1/home/tags/popular"); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -134,7 +138,7 @@ public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { try { - if(!vo.getApiSecretKey().equals(API_SECRET_KEY)) { + if(!apiSecretKey.equals(API_SECRET_KEY)) { throw new MainPageException("100", "잘못된 접근입니다."); } @@ -174,10 +178,9 @@ public ApiResponse selectPopularTagList(@RequestBody DefaultVO vo) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 지역 목록이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/popular/region/list") - public ApiResponse selectPopularRegionList(@RequestBody DefaultVO vo) { - - logger.info("CALL /main/page/popular/region/list"); + @GetMapping("/regions/popular") + public ApiResponse selectPopularRegionList(@RequestHeader("API-KEY") String apiSecretKey) { + logger.info("CALL /api/v1/home/regions/popular"); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -185,7 +188,7 @@ public ApiResponse selectPopularRegionList(@RequestBody DefaultVO vo) { try { - if(!vo.getApiSecretKey().equals(API_SECRET_KEY)) { + if(!apiSecretKey.equals(API_SECRET_KEY)) { throw new MainPageException("100", "잘못된 접근입니다."); } diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index 783cf97..3338461 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -4,8 +4,10 @@ import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.basic.join.dto.JoinDTO; -import com.barogagi.member.basic.join.dto.UserIdCheckDTO; import com.barogagi.member.basic.join.dto.JoinRequestDTO; +import com.barogagi.member.login.exception.InvalidRefreshTokenException; +import com.barogagi.member.login.service.AccountService; +import com.barogagi.member.login.service.AuthService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; @@ -18,13 +20,18 @@ import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; +import java.util.Map; +import java.util.Optional; + @Tag(name = "일반 회원가입", description = "일반 회원가입 관련 API") @RestController -@RequestMapping("/membership/join") +@RequestMapping("/api/v1/users") public class JoinController { private static final Logger logger = LoggerFactory.getLogger(JoinController.class); private final JoinService joinService; + private final AccountService accountService; + private final AuthService authService; private final InputValidate inputValidate; private final EncryptUtil encryptUtil; private final Validator validator; @@ -35,12 +42,16 @@ public class JoinController { @Autowired public JoinController(Environment environment, JoinService joinService, + AccountService accountService, + AuthService authService, InputValidate inputValidate, EncryptUtil encryptUtil, Validator validator, PasswordConfig passwordConfig) { this.API_SECRET_KEY = environment.getProperty("api.secret-key"); this.joinService = joinService; + this.accountService = accountService; + this.authService = authService; this.inputValidate = inputValidate; this.encryptUtil = encryptUtil; this.validator = validator; @@ -56,31 +67,30 @@ public JoinController(Environment environment, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "해당 아이디 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/userId/check") - public ApiResponse checkUserId(@RequestBody UserIdCheckDTO userIdCheckDTO) { - - logger.info("CALL /membership/join/basic/membership/userId/check"); - logger.info("[input] API_SECRET_KEY={}", userIdCheckDTO.getApiSecretKey()); + @GetMapping("/userid/exists") + public ApiResponse checkUserId(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String userId) { + logger.info("CALL /api/v1/users/userId/exists"); + logger.info("[input] API_SECRET_KEY={}", apiSecretKey); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; String message = ""; try { - if(userIdCheckDTO.getApiSecretKey().equals(API_SECRET_KEY)){ + if(apiSecretKey.equals(API_SECRET_KEY)){ - if(inputValidate.isEmpty(userIdCheckDTO.getUserId())) { + if(inputValidate.isEmpty(userId)) { resultCode = "101"; message = "아이디를 입력해주세요."; } else{ - if(!validator.isValidId(userIdCheckDTO.getUserId())) { + if(!validator.isValidId(userId)) { resultCode = "102"; message = "적합한 아이디가 아닙니다."; } else { JoinDTO joinDTO = new JoinDTO(); - joinDTO.setUserId(userIdCheckDTO.getUserId()); + joinDTO.setUserId(userId); int checkUserId = joinService.checkUserId(joinDTO); logger.info("@@ checkUserId={}", checkUserId); @@ -123,10 +133,10 @@ public ApiResponse checkUserId(@RequestBody UserIdCheckDTO userIdCheckDTO) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원가입에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/insert") + @PostMapping public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestDTO){ - logger.info("CALL /membership/join/basic/membership/insert"); + logger.info("CALL /api/v1/users"); logger.info("[input] API_SECRET_KEY={}", joinRequestDTO.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); @@ -222,11 +232,12 @@ public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestD @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "이미 존재하는 닉네임입니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/check/duplicate/nickname") - public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ + @GetMapping("/nickname/exists") + public ApiResponse checkDuplicateNickname(@RequestHeader("API-KEY") String apiSecretKey, + @RequestParam String nickname){ - logger.info("CALL /membership/join/check/duplicate/nickname"); - logger.info("[input] API_SECRET_KEY={}", nickNameDTO.getApiSecretKey()); + logger.info("CALL /api/v1/user/nickname/exists"); + logger.info("[input] API_SECRET_KEY={}", apiSecretKey); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -234,10 +245,10 @@ public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ try { - if(nickNameDTO.getApiSecretKey().equals(API_SECRET_KEY)){ + if(apiSecretKey.equals(API_SECRET_KEY)){ // 필수 입력값 - if(inputValidate.isEmpty(nickNameDTO.getNickName())){ + if(inputValidate.isEmpty(nickname)){ // 필수 입력값 중 빈 값이 존재. insert 중지 resultCode = "101"; @@ -245,10 +256,14 @@ public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ } else{ - if(!validator.isValidNickname(nickNameDTO.getNickName())) { + if(!validator.isValidNickname(nickname)) { resultCode = "102"; message = "적합하지 않는 닉네임입니다."; } else { + + NickNameDTO nickNameDTO = new NickNameDTO(); + nickNameDTO.setNickName(nickname); + int nickNameCnt = joinService.checkNickName(nickNameDTO); logger.info("nickNameCnt={}", nickNameCnt); if(nickNameCnt > 0) { @@ -277,4 +292,66 @@ public ApiResponse checkDuplicateNickname(@RequestBody NickNameDTO nickNameDTO){ return apiResponse; } + + @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "refresh token이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) + @DeleteMapping("/me") + public ApiResponse deleteMe(@RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body) { + + logger.info("CALL /api/v1/users/me"); + + ApiResponse apiResponse = new ApiResponse(); + String resultCode = ""; + String message = ""; + + try { + + String refreshToken = Optional.ofNullable(refreshHeader) + .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) + .orElse(null); + + if (refreshToken == null || refreshToken.isBlank()) { + throw new InvalidRefreshTokenException("100", "refresh token이 존재하지 않습니다."); + } + + // refresh token을 이용해서 membershipNo 구하기 + + Map resultMap = authService.selectUserInfoByToken(refreshToken); + if(!resultMap.get("resultCode").equals("200")) { + throw new InvalidRefreshTokenException(resultMap.get("resultCode"), resultMap.get("message")); + } + + String membershipNo = resultMap.get("membershipNo"); + + int deleteResult = accountService.deleteMyAccount(membershipNo); + if(deleteResult > 0) { + resultCode = "200"; + message = "회원 탈퇴되었습니다."; + } else { + resultCode = "300"; + message = "회원 탈퇴 실패하였습니다."; + } + + } catch (InvalidRefreshTokenException ex) { + resultCode = ex.getCode(); + message = ex.getMessage(); + + } catch (Exception e) { + logger.error("error", e); + resultCode = "400"; + message = "오류가 발생하였습니다."; + + } finally { + apiResponse.setResultCode(resultCode); + apiResponse.setMessage(message); + } + + return apiResponse; + } } diff --git a/src/main/java/com/barogagi/member/basic/join/dto/UserIdCheckDTO.java b/src/main/java/com/barogagi/member/basic/join/dto/UserIdCheckDTO.java deleted file mode 100644 index 1491630..0000000 --- a/src/main/java/com/barogagi/member/basic/join/dto/UserIdCheckDTO.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.barogagi.member.basic.join.dto; - -import com.barogagi.config.vo.DefaultVO; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; -import jakarta.validation.constraints.Size; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class UserIdCheckDTO extends DefaultVO { - @NotBlank(message = "사용자 ID는 필수 입력값입니다.") - @Size(min = 4, max = 20, message = "사용자 ID는 4자 이상 20자 이하여야 합니다.") - @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "사용자 ID는 영문과 숫자만 사용 가능합니다.") - private String userId = ""; -} diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index f15660e..ec586dd 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -8,19 +8,19 @@ import com.barogagi.member.info.service.MemberService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; +import com.barogagi.util.MembershipUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; @Tag(name = "회원 정보", description = "회원 정보 관련 API") @RestController -@RequestMapping("/info") +@RequestMapping("/api/v1/members") public class InfoController { private static final Logger logger = LoggerFactory.getLogger(InfoController.class); @@ -29,14 +29,17 @@ public class InfoController { private final JoinService joinService; private final EncryptUtil encryptUtil; + private final MembershipUtil membershipUtil; public InfoController(MemberService memberService, JoinService joinService, - EncryptUtil encryptUtil) { + EncryptUtil encryptUtil, + MembershipUtil membershipUtil) { this.memberService = memberService; this.joinService = joinService; this.encryptUtil = encryptUtil; + this.membershipUtil = membershipUtil; } @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.", @@ -46,9 +49,9 @@ public InfoController(MemberService memberService, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 정보 조회가 완료되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/member") + @GetMapping public ApiResponse selectMemberInfo(HttpServletRequest request) { - logger.info("CALL /info/member"); + logger.info("CALL /api/v1/members"); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -56,12 +59,14 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { try { - Object membershipNoAttr = request.getAttribute("membershipNo"); - if(membershipNoAttr == null) { - throw new MemberInfoException("401", "접근 권한이 존재하지 않습니다."); + // 회원번호 구하기 + Map membershipNoInfo = membershipUtil.membershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message"))); } - String membershipNo = String.valueOf(membershipNoAttr); + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); // 회원 정보 조회 Member memberInfo = memberService.findByMembershipNo(membershipNo); @@ -107,9 +112,9 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용자 정보 수정 완료하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/member/update") + @PatchMapping public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody MemberRequestDTO memberRequestDto) { - logger.info("CALL /info/member/update"); + logger.info("CALL /api/v1/members"); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; @@ -120,12 +125,16 @@ public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody Mem logger.info("param gender={}", memberRequestDto.getGender()); logger.info("param nickName={}", memberRequestDto.getNickName()); - String membershipNo = String.valueOf(request.getAttribute("membershipNo")); - logger.info("@@ membershipNo.isEmpty()={}", membershipNo.isEmpty()); - if (membershipNo.isEmpty()) { - throw new MemberInfoException("401", "접근 권한이 존재하지 않습니다."); + // 회원번호 구하기 + Map membershipNoInfo = membershipUtil.membershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message"))); } + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); + logger.info("@@ membershipNo={}", membershipNo); + // 회원 정보 조회 Member memberInfo = memberService.findByMembershipNo(membershipNo); if(null == memberInfo) { diff --git a/src/main/java/com/barogagi/member/login/controller/AuthController.java b/src/main/java/com/barogagi/member/login/controller/AuthController.java index 70dafc1..3d024e2 100644 --- a/src/main/java/com/barogagi/member/login/controller/AuthController.java +++ b/src/main/java/com/barogagi/member/login/controller/AuthController.java @@ -31,128 +31,6 @@ public AuthController(AuthService authService, this.authService = authService; this.accountService = accountService; } - - @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") - @PostMapping("/refresh") - public ResponseEntity> refresh( - @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body - ) { - - logger.info("CALL /auth/refresh"); - - try { - String rt = Optional.ofNullable(refreshHeader) - .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) - .orElse(null); - - if (rt == null || rt.isBlank()) { - return ResponseEntity.status(401).body(Map.of("error", "refresh_required")); - } - - TokenPair pair = authService.rotate(rt); // ❗️핵심 로직 (아래 2) 참조) - - return ResponseEntity.ok(Map.of( - "accessToken", pair.accessToken(), - "accessTokenExpiresIn", pair.accessTokenExpiresIn(), - "refreshToken", pair.refreshToken(), - "refreshTokenExpiresIn", pair.refreshTokenExpiresIn() - )); - - } catch (InvalidRefreshTokenException e) { - return ResponseEntity.status(HttpServletResponse.SC_UNAUTHORIZED) - .body(Map.of( - "resultCode", "400", - "errorCode", e.getCode(), - "message", e.getMessage(), - "needLogin", true - )); - } - } - - /** - * 현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE - * 입력 경로: - * - 헤더: Refresh-Token: - * - 바디: { "refreshToken": "" } - */ - @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") - @PostMapping("/logout") - public ResponseEntity> logout( - @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body - ) { - String refresh = refreshHeader; - if ((refresh == null || refresh.isBlank()) && body != null) { - refresh = body.get("refreshToken"); - } - if (refresh != null && !refresh.isBlank()) { - authService.logout(refresh); // DB REVOKE - } - return ResponseEntity.ok(Map.of("resultCode", "200", "message", "로그아웃 되었습니다.")); - } - - @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", - responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "refresh token이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") - }) - @PostMapping("/member/delete") - public ApiResponse deleteMe(@RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body) { - - logger.info("CALL /auth/member/delete"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - String refreshToken = Optional.ofNullable(refreshHeader) - .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) - .orElse(null); - - if (refreshToken == null || refreshToken.isBlank()) { - throw new InvalidRefreshTokenException("100", "refresh token이 존재하지 않습니다."); - } - - // refresh token을 이용해서 membershipNo 구하기 - - Map resultMap = authService.selectUserInfoByToken(refreshToken); - if(!resultMap.get("resultCode").equals("200")) { - throw new InvalidRefreshTokenException(resultMap.get("resultCode"), resultMap.get("message")); - } - - String membershipNo = resultMap.get("membershipNo"); - - int deleteResult = accountService.deleteMyAccount(membershipNo); - if(deleteResult > 0) { - resultCode = "200"; - message = "회원 탈퇴되었습니다."; - } else { - resultCode = "300"; - message = "회원 탈퇴 실패하였습니다."; - } - - } catch (InvalidRefreshTokenException ex) { - resultCode = ex.getCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; - } } diff --git a/src/main/java/com/barogagi/member/login/controller/LoginController.java b/src/main/java/com/barogagi/member/login/controller/LoginController.java index d00ac21..3e56b77 100644 --- a/src/main/java/com/barogagi/member/login/controller/LoginController.java +++ b/src/main/java/com/barogagi/member/login/controller/LoginController.java @@ -4,41 +4,35 @@ import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.service.MemberService; import com.barogagi.member.login.dto.*; +import com.barogagi.member.login.exception.InvalidRefreshTokenException; import com.barogagi.member.login.exception.LoginException; import com.barogagi.member.login.service.AuthService; import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; -import com.barogagi.util.JwtUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; +import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; -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.RestController; +import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; -@Tag(name = "일반 로그인", description = "일반 로그인 관련 API") +@Tag(name = "일반 로그인 & 토큰 재발급", description = "일반 로그인, 토큰 재발급 관련 API") @RestController -@RequestMapping("/login") +@RequestMapping("/api/v1/auth") public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); private final InputValidate inputValidate; - private final PasswordConfig passwordConfig; private final EncryptUtil encryptUtil; - private final JwtUtil jwtUtil; private final LoginService loginService; private final MemberService memberService; @@ -46,6 +40,8 @@ public class LoginController { private final PasswordEncoder passwordEncoder; + private final PasswordConfig passwordConfig; + private final String API_SECRET_KEY; @Autowired @@ -55,9 +51,8 @@ public LoginController(Environment environment, LoginService loginService, MemberService memberService, AuthService authService, - JwtUtil jwtUtil, - PasswordConfig passwordConfig, - PasswordEncoder passwordEncoder){ + PasswordEncoder passwordEncoder, + PasswordConfig passwordConfig){ this.API_SECRET_KEY = environment.getProperty("api.secret-key"); this.inputValidate = inputValidate; @@ -65,9 +60,8 @@ public LoginController(Environment environment, this.loginService = loginService; this.memberService = memberService; this.authService = authService; - this.jwtUtil = jwtUtil; - this.passwordConfig = passwordConfig; this.passwordEncoder = passwordEncoder; + this.passwordConfig = passwordConfig; } @Operation(summary = "로그인", description = "로그인 기능입니다.", @@ -79,10 +73,10 @@ public LoginController(Environment environment, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/login") + @PostMapping("/login") public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ - logger.info("CALL /login/basic/membership/login"); + logger.info("CALL /api/v1/auth/login"); logger.info("[input] API_SECRET_KEY={}", loginRequestDTO.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); @@ -155,10 +149,10 @@ public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/userId/search") + @PostMapping("/find-user") public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequestDTO){ - logger.info("CALL /login/basic/membership/userId/search"); + logger.info("CALL /api/v1/auth/find-user"); logger.info("[input] API_SECRET_KEY={}", searchUserIdRequestDTO.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); @@ -221,10 +215,10 @@ public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequest @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/basic/membership/password/update") + @PostMapping("/password-reset/confirm") public ApiResponse updatePassword(@RequestBody LoginDTO vo){ - logger.info("CALL /login/basic/membership/password/update"); + logger.info("CALL /api/v1/auth/password-reset/confirm"); logger.info("[input] API_SECRET_KEY={}", vo.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); @@ -240,7 +234,8 @@ public ApiResponse updatePassword(@RequestBody LoginDTO vo){ } else { // 비밀번호 암호화 - vo.setPassword(encryptUtil.hashEncodeString(vo.getPassword())); + String encodedPassword = passwordConfig.passwordEncoder().encode(vo.getPassword()); + vo.setPassword(encodedPassword); // 비밀번호 update int updatePassword = loginService.updatePassword(vo); @@ -270,4 +265,64 @@ public ApiResponse updatePassword(@RequestBody LoginDTO vo){ } return apiResponse; } + + @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") + @PostMapping("/token/refresh") + public ResponseEntity> refresh( + @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body + ) { + + logger.info("CALL /api/v1/auth/token/refresh"); + + try { + String rt = Optional.ofNullable(refreshHeader) + .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) + .orElse(null); + + if (rt == null || rt.isBlank()) { + return ResponseEntity.status(401).body(Map.of("error", "refresh_required")); + } + + TokenPair pair = authService.rotate(rt); // ❗️핵심 로직 (아래 2) 참조) + + return ResponseEntity.ok(Map.of( + "accessToken", pair.accessToken(), + "accessTokenExpiresIn", pair.accessTokenExpiresIn(), + "refreshToken", pair.refreshToken(), + "refreshTokenExpiresIn", pair.refreshTokenExpiresIn() + )); + + } catch (InvalidRefreshTokenException e) { + return ResponseEntity.status(HttpServletResponse.SC_UNAUTHORIZED) + .body(Map.of( + "resultCode", "400", + "errorCode", e.getCode(), + "message", e.getMessage(), + "needLogin", true + )); + } + } + + /** + * 현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE + * 입력 경로: + * - 헤더: Refresh-Token: + * - 바디: { "refreshToken": "" } + */ + @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") + @PostMapping("/logout") + public ResponseEntity> logout( + @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, + @RequestBody(required = false) Map body + ) { + String refresh = refreshHeader; + if ((refresh == null || refresh.isBlank()) && body != null) { + refresh = body.get("refreshToken"); + } + if (refresh != null && !refresh.isBlank()) { + authService.logout(refresh); // DB REVOKE + } + return ResponseEntity.ok(Map.of("resultCode", "200", "message", "로그아웃 되었습니다.")); + } } diff --git a/src/main/java/com/barogagi/terms/controller/TermsController.java b/src/main/java/com/barogagi/terms/controller/TermsController.java index a839aa3..f726d43 100644 --- a/src/main/java/com/barogagi/terms/controller/TermsController.java +++ b/src/main/java/com/barogagi/terms/controller/TermsController.java @@ -19,13 +19,13 @@ @Tag(name = "약관", description = "약관 관련 API") @RestController -@RequestMapping("/terms") +@RequestMapping("/api/v1/terms") public class TermsController { private static final Logger logger = LoggerFactory.getLogger(TermsController.class); - private InputValidate inputValidate; - private TermsService termsService; - private LoginService loginService; + private final InputValidate inputValidate; + private final TermsService termsService; + private final LoginService loginService; private final String API_SECRET_KEY; @@ -38,7 +38,7 @@ public TermsController(Environment environment, InputValidate inputValidate, this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } - @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.", + @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.
회원가입 시 사용할 경우 termsType 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 조회에 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @@ -46,22 +46,24 @@ public TermsController(Environment environment, InputValidate inputValidate, @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "약관이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/list") - public ApiResponse termsList(@RequestBody TermsInputDTO termsInputDTO){ - logger.info("CALL /terms/list"); - logger.info("[input] API_SECRET_KEY={}", termsInputDTO.getApiSecretKey()); + @GetMapping + public ApiResponse termsList(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String termsType){ + logger.info("CALL /api/v1/terms"); + logger.info("[input] API_SECRET_KEY={}", apiSecretKey); ApiResponse apiResponse = new ApiResponse(); String resultCode = ""; String message = ""; try { - if(termsInputDTO.getApiSecretKey().equals(API_SECRET_KEY)) { + if(apiSecretKey.equals(API_SECRET_KEY)) { - if(inputValidate.isEmpty(termsInputDTO.getTermsType())) { + if(inputValidate.isEmpty(termsType)) { resultCode = "101"; message = "조회하실 약관의 종류 값이 존재하지 않습니다."; } else { + TermsInputDTO termsInputDTO = new TermsInputDTO(); + termsInputDTO.setTermsType(termsType); List termsList = termsService.selectTermsList(termsInputDTO); int termsCnt = termsList.size(); @@ -102,9 +104,9 @@ public ApiResponse termsList(@RequestBody TermsInputDTO termsInputDTO){ @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "약관 저장에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) - @PostMapping("/agree/insert") + @PostMapping("/terms-agreements") public ApiResponse insertTermsAgree(@RequestBody TermsDTO termsDTO) { - logger.info("CALL /agree/insert"); + logger.info("CALL /api/v1/terms/{userId}/terms-agreements"); logger.info("[input] API_SECRET_KEY={}", termsDTO.getApiSecretKey()); ApiResponse apiResponse = new ApiResponse(); diff --git a/src/main/java/com/barogagi/util/MembershipUtil.java b/src/main/java/com/barogagi/util/MembershipUtil.java new file mode 100644 index 0000000..ab18258 --- /dev/null +++ b/src/main/java/com/barogagi/util/MembershipUtil.java @@ -0,0 +1,42 @@ +package com.barogagi.util; + +import com.barogagi.util.exception.BasicException; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class MembershipUtil { + + public Map membershipNoService(HttpServletRequest request) { + + Map resultMap = new HashMap<>(); + + String resultCode = ""; + String message = ""; + String membershipNo = ""; + + try { + Object membershipNoAttr = request.getAttribute("membershipNo"); + if(membershipNoAttr == null) { + throw new BasicException("401", "접근 권한이 존재하지 않습니다."); + } + + membershipNo = String.valueOf(membershipNoAttr); + resultCode = "200"; + message = "회원 번호가 존재합니다."; + + } catch (BasicException ex) { + resultCode = ex.getResultCode(); + message = ex.getMessage(); + } finally { + resultMap.put("resultCode", resultCode); + resultMap.put("message", message); + resultMap.put("membershipNo", membershipNo); + } + + return resultMap; + } +} diff --git a/src/main/java/com/barogagi/util/exception/BasicException.java b/src/main/java/com/barogagi/util/exception/BasicException.java new file mode 100644 index 0000000..86bf3da --- /dev/null +++ b/src/main/java/com/barogagi/util/exception/BasicException.java @@ -0,0 +1,14 @@ +package com.barogagi.util.exception; + +import lombok.Getter; + +@Getter +public class BasicException extends RuntimeException { + + private final String resultCode; + + public BasicException(String resultCode, String message) { + super(message); + this.resultCode = resultCode; + } +} From 4d026847ab4a2017979abcf1d57a76b43a1b229c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=85=B8=EB=8B=A4=EB=AF=BC?= Date: Fri, 19 Dec 2025 22:18:27 +0900 Subject: [PATCH 48/79] =?UTF-8?q?style:=20api=20url=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=20(#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/barogagi/plan/controller/PlaceController.java | 2 +- .../java/com/barogagi/region/controller/RegionController.java | 4 ++-- .../com/barogagi/schedule/controller/ScheduleController.java | 2 +- src/main/java/com/barogagi/tag/controller/TagController.java | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/barogagi/plan/controller/PlaceController.java b/src/main/java/com/barogagi/plan/controller/PlaceController.java index 232f412..f589273 100644 --- a/src/main/java/com/barogagi/plan/controller/PlaceController.java +++ b/src/main/java/com/barogagi/plan/controller/PlaceController.java @@ -16,7 +16,7 @@ @Tag(name = "장소", description = "장소 관련 API") @RestController -@RequestMapping("/place") +@RequestMapping("/api/v1/place") public class PlaceController { private static final Logger logger = LoggerFactory.getLogger(ScheduleController.class); diff --git a/src/main/java/com/barogagi/region/controller/RegionController.java b/src/main/java/com/barogagi/region/controller/RegionController.java index 8a2312e..e971090 100644 --- a/src/main/java/com/barogagi/region/controller/RegionController.java +++ b/src/main/java/com/barogagi/region/controller/RegionController.java @@ -26,7 +26,7 @@ @Tag(name = "지역", description = "지역 관련 API") @RestController -@RequestMapping("/region") +@RequestMapping("/api/v1/region") public class RegionController { private static final Logger logger = LoggerFactory.getLogger(RegionController.class); @@ -102,7 +102,7 @@ public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 @Operation(summary = "주소 목록을 검색하는 기능", description = "주소 목록을 검색하는 기능입니다.
" + "REGION table에 저장된 값 중, level 1부터 4까지 가장 정확도 높은 순으로 지역명 최대 10개를 리턴합니다.
" + "행정구역 단계(시/도, 시/군/구, 동/면/리)를 조합하여 결과를 반환하며, 중복되는 상위 주소(예: '서울특별시 강남구')는 한 번만 표시됩니다.") - @GetMapping("/searchList") + @GetMapping("/search-list") public ApiResponse searchList(@Parameter(description = "검색할 주소명", example = "강남") @RequestParam String regionQuery) { logger.info("CALL /region/searchList"); diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 87a5922..4aa2fe9 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -29,7 +29,7 @@ @Tag(name = "일정", description = "일정 관련 API") @RestController -@RequestMapping("/schedule") +@RequestMapping("/api/v1//schedule") public class ScheduleController { private static final Logger logger = LoggerFactory.getLogger(ScheduleController.class); diff --git a/src/main/java/com/barogagi/tag/controller/TagController.java b/src/main/java/com/barogagi/tag/controller/TagController.java index bac8f4e..19cf516 100644 --- a/src/main/java/com/barogagi/tag/controller/TagController.java +++ b/src/main/java/com/barogagi/tag/controller/TagController.java @@ -17,7 +17,7 @@ @Tag(name = "태그", description = "태그 관련 API") @RestController -@RequestMapping("/tag") +@RequestMapping("/api/v1/tag") public class TagController { private static final Logger logger = LoggerFactory.getLogger(TagController.class); @@ -41,7 +41,7 @@ public TagController(Environment environment, InputValidate inputValidate, "- 상세 일정 태그(P): 해당 일정의 카테고리 번호(categoryNum)를 전달하세요.
" + "검색 결과는 최대 10개의 태그를 반환합니다." ) - @PostMapping("/searchList") + @PostMapping("/search-list") public ApiResponse searchList(@RequestBody TagSearchReqDTO tagSearchReqDTO) { logger.info("CALL /tag/searchList"); From 2957734360b0e7debd4c5640b9c40d9098c25b07 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sun, 21 Dec 2025 17:54:47 +0900 Subject: [PATCH 49/79] =?UTF-8?q?chore:=20=EC=93=B0=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20import=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/controller/ScheduleController.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 4aa2fe9..34fc92b 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -1,31 +1,21 @@ package com.barogagi.schedule.controller; -//import com.barogagi.member.join.vo.JoinVO; -//import com.barogagi.member.join.vo.UserIdCheckVO; -import com.barogagi.member.login.controller.LoginController; -import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.query.service.PlanQueryService; -import com.barogagi.plan.query.vo.PlanDetailVO; -import com.barogagi.region.dto.RegionRegistReqDTO; -import com.barogagi.region.dto.RegionSearchResDTO; import com.barogagi.response.ApiResponse; import com.barogagi.schedule.command.service.ScheduleCommandService; import com.barogagi.schedule.dto.*; import com.barogagi.schedule.query.service.ScheduleQueryService; -import com.barogagi.schedule.query.vo.ScheduleDetailVO; import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; @Tag(name = "일정", description = "일정 관련 API") @RestController From 5fd0d4e4539bbf8f90c2bd928d86227d20510849 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sun, 21 Dec 2025 20:43:03 +0900 Subject: [PATCH 50/79] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/barogagi/config/SecurityConfig.java | 2 +- .../com/barogagi/response/ApiResponse.java | 2 +- .../util/exception/BasicException.java | 10 +++++ .../barogagi/util/exception/ErrorCode.java | 37 +++++++++++++++++++ .../exception/GlobalExceptionHandler.java | 34 +++++++++++++++++ 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/barogagi/util/exception/ErrorCode.java create mode 100644 src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index 95c803e..b07721a 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -38,7 +38,7 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, "/webjars/**", "/login/oauth2/**", "/oauth2/**", - "/api/v1/auth/**", // ㄲ일반 회원가입 관련 + "/api/v1/auth/**", // 일반 회원가입 관련 "/api/v1/users/**", // 로그인 관련 "/api/v1/terms/**", // 약관 관련 "/api/v1/home/tags/popular", // 인기 태그 조회 diff --git a/src/main/java/com/barogagi/response/ApiResponse.java b/src/main/java/com/barogagi/response/ApiResponse.java index cb261b9..f5bd553 100644 --- a/src/main/java/com/barogagi/response/ApiResponse.java +++ b/src/main/java/com/barogagi/response/ApiResponse.java @@ -18,7 +18,7 @@ public class ApiResponse { public static ApiResponse success(T data, String message) { ApiResponse res = new ApiResponse<>(); - res.resultCode = "200"; + res.resultCode = "SUCCESS"; res.message = message; res.data = data; return res; diff --git a/src/main/java/com/barogagi/util/exception/BasicException.java b/src/main/java/com/barogagi/util/exception/BasicException.java index 86bf3da..198ad27 100644 --- a/src/main/java/com/barogagi/util/exception/BasicException.java +++ b/src/main/java/com/barogagi/util/exception/BasicException.java @@ -6,9 +6,19 @@ public class BasicException extends RuntimeException { private final String resultCode; + private final ErrorCode errorCode; + // 신규 생성자 + public BasicException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + this.resultCode = errorCode.getCode(); + } + + // 기존 생성자 public BasicException(String resultCode, String message) { super(message); this.resultCode = resultCode; + errorCode = null; } } diff --git a/src/main/java/com/barogagi/util/exception/ErrorCode.java b/src/main/java/com/barogagi/util/exception/ErrorCode.java new file mode 100644 index 0000000..9e365af --- /dev/null +++ b/src/main/java/com/barogagi/util/exception/ErrorCode.java @@ -0,0 +1,37 @@ +package com.barogagi.util.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor +public enum ErrorCode { + + // Common + INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON-500", "서버 오류가 발생했습니다."), + INVALID_REQUEST(HttpStatus.BAD_REQUEST, "COMMON-400", "잘못된 요청입니다."), + + // Membership + MEMBERSHIP_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBERSHIP-404", "멤버십 정보가 없습니다."), + MEMBERSHIP_SERVICE_FAIL(HttpStatus.BAD_GATEWAY, "MEMBERSHIP-502", "멤버십 서비스 호출에 실패했습니다."), + + // Schedule + SCHEDULE_SAVE_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "SCH-001", "일정 저장에 실패했습니다."), + SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "SCH-002", "일정 정보를 찾을 수 없습니다."), + SCHEDULE_ALREADY_DELETED(HttpStatus.NOT_FOUND, "SCH-003", "이미 삭제된 일정입니다."), + + // Tag + TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "TAG-001", "태그 정보를 찾을 수 없습니다."), + + // Item + ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "ITEM-001", "아이템 정보를 찾을 수 없습니다."), + + // Region + REGION_NOT_FOUND(HttpStatus.NOT_FOUND, "REGION-001", "지역 정보를 찾을 수 없습니다."); + + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..e897815 --- /dev/null +++ b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java @@ -0,0 +1,34 @@ +package com.barogagi.util.exception; + +import com.barogagi.response.ApiResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(BasicException.class) + public ResponseEntity> handleBasicException(BasicException e) { + + ErrorCode errorCode = e.getErrorCode(); + + return ResponseEntity + .status(errorCode.getStatus()) + .body(ApiResponse.error( + errorCode.getCode(), + errorCode.getMessage() + )); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleUnknown(Exception e) { + + return ResponseEntity + .status(ErrorCode.INTERNAL_ERROR.getStatus()) + .body(ApiResponse.error( + ErrorCode.INTERNAL_ERROR.getCode(), + ErrorCode.INTERNAL_ERROR.getMessage() + )); + } +} From dde73d4debffd22aa0d7eb1bc7fff789491c5aa2 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sun, 21 Dec 2025 20:43:37 +0900 Subject: [PATCH 51/79] =?UTF-8?q?feat:=20swagger=EC=97=90=20AccessToken,?= =?UTF-8?q?=20ApiKey=20=EC=84=B8=ED=8C=85=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20config=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/SwaggerConfig.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/SwaggerConfig.java b/src/main/java/com/SwaggerConfig.java index 85beadd..95c7828 100644 --- a/src/main/java/com/SwaggerConfig.java +++ b/src/main/java/com/SwaggerConfig.java @@ -1,13 +1,27 @@ package com; import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; import io.swagger.v3.oas.annotations.info.Info; +import io.swagger.v3.oas.annotations.security.SecurityScheme; import org.springframework.context.annotation.Configuration; @OpenAPIDefinition( info = @Info(title = "[PROJECT] barogagi API", version = "v1", description = "프로젝트 바로가기 API 명세서") ) - +@SecurityScheme( + name = "bearerAuth", + type = SecuritySchemeType.HTTP, + scheme = "bearer", + bearerFormat = "JWT" +) +@SecurityScheme( + name = "apiKeyAuth", + type = SecuritySchemeType.APIKEY, + in = SecuritySchemeIn.HEADER, + paramName = "API-KEY" +) @Configuration public class SwaggerConfig { } From ac7bb565ccfd337fb0095d3ecd07502e209a3df3 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sun, 21 Dec 2025 20:44:08 +0900 Subject: [PATCH 52/79] =?UTF-8?q?feat:=20membershipNo=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80,=20api=20=EC=97=90=EB=9F=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ScheduleCommandService.java | 220 +++++++++--------- .../controller/ScheduleController.java | 111 ++++++--- .../schedule/query/mapper/ScheduleMapper.java | 5 +- .../query/service/ScheduleQueryService.java | 16 +- .../schedule/query/vo/ScheduleDetailVO.java | 1 + .../query/vo/ScheduleMembershipNoVO.java | 13 ++ .../tag/command/entity/ScheduleTag.java | 4 + src/main/resources/mapper/ScheduleMapper.xml | 9 +- 8 files changed, 220 insertions(+), 159 deletions(-) create mode 100644 src/main/java/com/barogagi/schedule/query/vo/ScheduleMembershipNoVO.java diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index 4dd4755..d04ab29 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -42,6 +42,8 @@ import com.barogagi.tag.dto.TagRegistReqDTO; import com.barogagi.tag.dto.TagRegistResDTO; import com.barogagi.tag.query.service.TagQueryService; +import com.barogagi.util.exception.BasicException; +import com.barogagi.util.exception.ErrorCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -400,148 +402,134 @@ private PlanRegistResDTO handleUserPlan(PlanRegistReqDTO plan) { } - // 등록완료된 스케쥴의 num을 리턴 - public Integer saveSchedule(ScheduleRegistResDTO scheduleRegistResDTO) { - return saveScheduleInfo(scheduleRegistResDTO); - } - - - -// @Transactional @Transactional(propagation = Propagation.REQUIRES_NEW) - public Integer saveScheduleInfo(ScheduleRegistResDTO scheduleRegistResDTO){ + public Integer saveSchedule(ScheduleRegistResDTO scheduleRegistResDTO, String membershipNo) { logger.info("START DB SAVE!"); - try { - - // 1. Schedule - Schedule schedule = Schedule.builder() - .membershipNo("1") // todo. token에서 정보 가져오는 방식으로 수정 필요 - .scheduleNm(scheduleRegistResDTO.getScheduleNm()) - .startDate(scheduleRegistResDTO.getStartDate()) - .endDate(scheduleRegistResDTO.getEndDate()) - .radius(radius) - .delYn("N") - .build(); + // 1. Schedule + Schedule schedule = Schedule.builder() + .membershipNo(membershipNo) + .scheduleNm(scheduleRegistResDTO.getScheduleNm()) + .startDate(scheduleRegistResDTO.getStartDate()) + .endDate(scheduleRegistResDTO.getEndDate()) + .radius(radius) + .delYn("N") + .build(); - scheduleRepository.save(schedule); - logger.info("schedule Save! scheduleNum={}", schedule.getScheduleNum()); + scheduleRepository.save(schedule); + logger.info("schedule Save! scheduleNum={}", schedule.getScheduleNum()); - // 2. Schedule_tag - if (scheduleRegistResDTO.getScheduleTagRegistResDTOList() != null) { - logger.info("schedule save result id={}", schedule.getScheduleNum()); + // 2. Schedule_tag + if (scheduleRegistResDTO.getScheduleTagRegistResDTOList() != null) { + logger.info("schedule save result id={}", schedule.getScheduleNum()); - for (TagRegistResDTO tagReq : scheduleRegistResDTO.getScheduleTagRegistResDTOList()) { - Tag tag = tagRepository.findById(tagReq.getTagNum()) - .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagReq.getTagNum())); + for (TagRegistResDTO tagReq : scheduleRegistResDTO.getScheduleTagRegistResDTOList()) { + Tag tag = tagRepository.findById(tagReq.getTagNum()) + .orElseThrow(() -> new BasicException(ErrorCode.TAG_NOT_FOUND)); + + scheduleTagRepository.save( + ScheduleTag.builder() + .id(new ScheduleTagId(tag.getTagNum(), schedule.getScheduleNum())) + .membershipNo(membershipNo) + .schedule(schedule) + .tag(tag) + .build() + ); - scheduleTagRepository.save( - ScheduleTag.builder() - .id(new ScheduleTagId(tag.getTagNum(), schedule.getScheduleNum())) - .schedule(schedule) - .tag(tag) - .build() - ); - } } - logger.info("scheduleTag Save! scheduleNum={}", schedule.getScheduleNum()); + } + logger.info("scheduleTag Save! scheduleNum={}", schedule.getScheduleNum()); - // 3. Plan + Plan_tag + Plan_region + Place - for (int i = 0; i < scheduleRegistResDTO.getPlanRegistResDTOList().size(); i++) { + // 3. Plan + Plan_tag + Plan_region + Place + for (int i = 0; i < scheduleRegistResDTO.getPlanRegistResDTOList().size(); i++) { - PlanRegistResDTO planRes = scheduleRegistResDTO.getPlanRegistResDTOList().get(i); + PlanRegistResDTO planRes = scheduleRegistResDTO.getPlanRegistResDTOList().get(i); - Item item = itemRepository.findById(planRes.getItemNum()) - .orElseThrow(() -> new IllegalArgumentException("Item not found: " + planRes.getItemNum())); + Item item = itemRepository.findById(planRes.getItemNum()) + .orElseThrow(() -> new BasicException(ErrorCode.ITEM_NOT_FOUND)); - PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() - .membershipNo("1") // todo. token에서 정보 가져오는 방식으로 수정 필요 - .build(); + PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() + .membershipNo(membershipNo) + .build(); - // 3-1. Plan - Plan plan = Plan.builder() - .planNum(planRes.getPlanNum()) - .planNm(planRes.getPlanNm()) - .startTime(planRes.getStartTime()) - .endTime(planRes.getEndTime()) - .planLink(planRes.getPlanLink()) - .planDescription(planRes.getPlanDescription()) - .planAddress(planRes.getPlanAddress()) - .schedule(schedule) - .user(user) - .item(item) - .delYn("N") - .build(); + // 3-1. Plan + Plan plan = Plan.builder() + .planNum(planRes.getPlanNum()) + .planNm(planRes.getPlanNm()) + .startTime(planRes.getStartTime()) + .endTime(planRes.getEndTime()) + .planLink(planRes.getPlanLink()) + .planDescription(planRes.getPlanDescription()) + .planAddress(planRes.getPlanAddress()) + .schedule(schedule) + .user(user) + .item(item) + .delYn("N") + .build(); - planRepository.saveAndFlush(plan); - logger.info("Plan save! planNum={}", plan.getPlanNum()); + planRepository.saveAndFlush(plan); + logger.info("Plan save! planNum={}", plan.getPlanNum()); - // 3-2. Plan_tag - if (planRes.getPlanTagRegistResDTOList() != null) { + // 3-2. Plan_tag + if (planRes.getPlanTagRegistResDTOList() != null) { - for (TagRegistResDTO tagRes : planRes.getPlanTagRegistResDTOList()) { - Tag tag = tagRepository.findById(tagRes.getTagNum()) - .orElseThrow(() -> new IllegalArgumentException("Tag not found: " + tagRes.getTagNum())); + for (TagRegistResDTO tagRes : planRes.getPlanTagRegistResDTOList()) { + Tag tag = tagRepository.findById(tagRes.getTagNum()) + .orElseThrow(() -> new BasicException(ErrorCode.TAG_NOT_FOUND)); - PlanTag planTag = PlanTag.builder() - .id(new PlanTagId(tag.getTagNum(), plan.getPlanNum())) - .plan(plan) - .tag(tag) - .build(); + PlanTag planTag = PlanTag.builder() + .id(new PlanTagId(tag.getTagNum(), plan.getPlanNum())) + .plan(plan) + .tag(tag) + .build(); - planTagRepository.save(planTag); - } + planTagRepository.save(planTag); } + } - // 3-3. Plan_region - if (planRes.getRegionNum() != null) { - Region region = regionRepository.findById(planRes.getRegionNum()) - .orElseThrow(() -> new IllegalArgumentException("Region not found: " + planRes.getRegionNum())); - - PlanRegionId planRegionId = new PlanRegionId(plan.getPlanNum(), region.getRegionNum()); + // 3-3. Plan_region + if (planRes.getRegionNum() != null) { + Region region = regionRepository.findById(planRes.getRegionNum()) + .orElseThrow(() -> new BasicException(ErrorCode.REGION_NOT_FOUND)); - PlanRegion planRegion = PlanRegion.builder() - .id(planRegionId) - .region(region) - .plan(plan) - .build(); + PlanRegionId planRegionId = new PlanRegionId(plan.getPlanNum(), region.getRegionNum()); - planRegionRepository.save(planRegion); - logger.info("PlanRegion save! regionNum={}, planNum={}", region.getRegionNum(), plan.getPlanNum()); + PlanRegion planRegion = PlanRegion.builder() + .id(planRegionId) + .region(region) + .plan(plan) + .build(); - } + planRegionRepository.save(planRegion); + logger.info("PlanRegion save! regionNum={}, planNum={}", region.getRegionNum(), plan.getPlanNum()); - // 3-4. Place - if (planRes.getRegionNum() != null) { - Region region = regionRepository.findById(planRes.getRegionNum()) - .orElseThrow(() -> new IllegalArgumentException("Region not found: " + planRes.getRegionNum())); + } - Place place = Place.builder() - .region(region) - .regionNm(region.getRegionLevel3() != null ? region.getRegionLevel3() : region.getRegionLevel2()) - .address(planRes.getPlanAddress()) - .planLink(planRes.getPlanLink()) - .placeDescription(planRes.getPlanDescription()) - .plan(plan) - .build(); + // 3-4. Place + if (planRes.getRegionNum() != null) { + Region region = regionRepository.findById(planRes.getRegionNum()) + .orElseThrow(() -> new BasicException(ErrorCode.REGION_NOT_FOUND)); - placeRepository.save(place); - logger.info("Place save! planNum={}, regionNum={}", plan.getPlanNum(), planRes.getRegionNum()); + Place place = Place.builder() + .region(region) + .regionNm(region.getRegionLevel3() != null ? region.getRegionLevel3() : region.getRegionLevel2()) + .address(planRes.getPlanAddress()) + .planLink(planRes.getPlanLink()) + .placeDescription(planRes.getPlanDescription()) + .plan(plan) + .build(); - // 3-5. plan-place 동기화 - plan.toBuilder().place(place).build(); - } + placeRepository.save(place); + logger.info("Place save! planNum={}, regionNum={}", plan.getPlanNum(), planRes.getRegionNum()); + // 3-5. plan-place 동기화 + plan.toBuilder().place(place).build(); } - logger.info("END DB SAVE!"); - - return schedule.getScheduleNum(); - } catch (Exception e) { - logger.error(e.getMessage()); } + logger.info("END DB SAVE!"); - return null; + return schedule.getScheduleNum(); } @@ -554,7 +542,7 @@ public boolean deleteSchedule(Integer scheduleNum, String membershipNo) { schedule.markDeleted(); // del_yn=Y로 변경 return true; // 트랜잭션 커밋 시 자동 UPDATE } - return false; + throw new BasicException(ErrorCode.SCHEDULE_NOT_FOUND); } @Transactional @@ -562,7 +550,7 @@ public boolean updateSchedule(ScheduleRegistResDTO dto) { // 1) Schedule 조회 Schedule schedule = scheduleRepository.findById(String.valueOf(dto.getScheduleNum())) - .orElseThrow(() -> new IllegalArgumentException("Schedule not found: " + dto.getScheduleNum())); + .orElseThrow(() -> new BasicException(ErrorCode.SCHEDULE_NOT_FOUND)); // 2) Schedule 기본 정보 업데이트 schedule.updateBasicInfo( @@ -577,7 +565,7 @@ public boolean updateSchedule(ScheduleRegistResDTO dto) { if (dto.getScheduleTagRegistResDTOList() != null) { for (TagRegistResDTO tagReq : dto.getScheduleTagRegistResDTOList()) { Tag tag = tagRepository.findById(tagReq.getTagNum()) - .orElseThrow(() -> new IllegalArgumentException("Tag not found")); + .orElseThrow(() -> new BasicException(ErrorCode.TAG_NOT_FOUND)); scheduleTagRepository.save( ScheduleTag.builder() @@ -601,7 +589,7 @@ public boolean updateSchedule(ScheduleRegistResDTO dto) { for (PlanRegistResDTO planRes : dto.getPlanRegistResDTOList()) { Item item = itemRepository.findById(planRes.getItemNum()) - .orElseThrow(() -> new IllegalArgumentException("Item not found")); + .orElseThrow(() -> new BasicException(ErrorCode.ITEM_NOT_FOUND)); PlanUserMembershipInfo user = PlanUserMembershipInfo.builder() .membershipNo("1") @@ -627,7 +615,7 @@ public boolean updateSchedule(ScheduleRegistResDTO dto) { if (planRes.getPlanTagRegistResDTOList() != null) { for (TagRegistResDTO tagRes : planRes.getPlanTagRegistResDTOList()) { Tag tag = tagRepository.findById(tagRes.getTagNum()) - .orElseThrow(() -> new IllegalArgumentException("Tag not found")); + .orElseThrow(() -> new BasicException(ErrorCode.TAG_NOT_FOUND)); planTagRepository.save( new PlanTag(new PlanTagId(tag.getTagNum(), plan.getPlanNum()), plan, tag) @@ -638,7 +626,7 @@ public boolean updateSchedule(ScheduleRegistResDTO dto) { // PlanRegion + Place 저장 if (planRes.getRegionNum() != null) { Region region = regionRepository.findById(planRes.getRegionNum()) - .orElseThrow(() -> new IllegalArgumentException("Region not found")); + .orElseThrow(() -> new BasicException(ErrorCode.REGION_NOT_FOUND)); // PlanRegion PlanRegionId regionId = new PlanRegionId(plan.getPlanNum(), region.getRegionNum()); diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 34fc92b..a4154a1 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -6,20 +6,28 @@ import com.barogagi.schedule.dto.*; import com.barogagi.schedule.query.service.ScheduleQueryService; import com.barogagi.util.InputValidate; +import com.barogagi.util.MembershipUtil; +import com.barogagi.util.exception.BasicException; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Map; + @Tag(name = "일정", description = "일정 관련 API") @RestController -@RequestMapping("/api/v1//schedule") +@RequestMapping("/api/v1/schedule") +@SecurityRequirement(name = "bearerAuth") +@SecurityRequirement(name = "apiKeyAuth") public class ScheduleController { private static final Logger logger = LoggerFactory.getLogger(ScheduleController.class); @@ -30,31 +38,46 @@ public class ScheduleController { private final PlanQueryService planQueryService; private final String API_SECRET_KEY; + private final MembershipUtil membershipUtil; public ScheduleController(Environment environment, InputValidate inputValidate, ScheduleQueryService scheduleQueryService, ScheduleCommandService scheduleCommandService, - PlanQueryService planQueryService) { + PlanQueryService planQueryService, MembershipUtil membershipUtil) { this.API_SECRET_KEY = environment.getProperty("api.secret-key"); this.inputValidate = inputValidate; this.scheduleQueryService = scheduleQueryService; this.scheduleCommandService = scheduleCommandService; this.planQueryService = planQueryService; + this.membershipUtil = membershipUtil; } @Operation(summary = "내 일정 목록 조회 기능", description = "일정 목록을 조회하는 기능입니다.") @GetMapping("/list") - public ApiResponse getScheduleList() { + public ApiResponse getScheduleList(HttpServletRequest request) { - logger.info("CALL /schedule/list"); + logger.info("CALL /api/v1/schedule/list"); ScheduleListGroupResDTO result; try { - // TODO. token으로 사용자 확인 후, 해당 사용자의 일정만 조회하도록 수정해야 함 + + // token으로 membershipNo 조회 + Map resultMap = membershipUtil.membershipNoService(request); + String resultCode = String.valueOf(resultMap.get("resultCode")); + if (!"200".equals(resultCode)) { + throw new BasicException( + resultCode, + String.valueOf(resultMap.get("message")) + ); + } + String membershipNo = String.valueOf(resultMap.get("membershipNo")); + //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ - result = scheduleQueryService.getScheduleList(1); + result = scheduleQueryService.getScheduleList(membershipNo); + } catch (BasicException e) { + return ApiResponse.error(e.getResultCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 목록 조회 실패"); } @@ -66,22 +89,25 @@ public ApiResponse getScheduleList() { @Operation(summary = "일정 상세 조회 기능", description = "일정을 상세 조회하는 기능입니다.") @GetMapping("/detail") public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 번호", example = "1") - @RequestParam Integer scheduleNum) { + @RequestParam Integer scheduleNum, HttpServletRequest request) { - logger.info("CALL /schedule/detail"); + logger.info("CALL /api/v1/schedule/detail"); logger.info("[input] scheduleNum={}", scheduleNum); - ScheduleDetailResDTO result; - try { - // TODO. 해당 사용자의 일정이 맞는지도 체크해야 함 - //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ - result = scheduleQueryService.getScheduleDetail(scheduleNum); - - } catch (Exception e) { - return ApiResponse.error("404", "일정 조회 실패"); + // token으로 membershipNo 조회 + Map resultMap = membershipUtil.membershipNoService(request); + String resultCode = String.valueOf(resultMap.get("resultCode")); + if (!"200".equals(resultCode)) { + throw new BasicException( + resultCode, + String.valueOf(resultMap.get("message")) + ); } + String membershipNo = String.valueOf(resultMap.get("membershipNo")); + + ScheduleDetailResDTO result = scheduleQueryService.getScheduleDetail(scheduleNum, membershipNo); return ApiResponse.success(result, "일정 조회 성공"); } @@ -164,7 +190,7 @@ public ApiResponse createSchedule( @RequestBody ScheduleRegistReqDTO scheduleRegistReqDTO ) { - logger.info("CALL /schedule/create"); + logger.info("CALL /api/v1/schedule/create"); logger.info("[input] scheduleRegistReqDTO={}", scheduleRegistReqDTO); ScheduleRegistResDTO result; @@ -172,6 +198,8 @@ public ApiResponse createSchedule( //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ result = scheduleCommandService.createSchedule(scheduleRegistReqDTO); + } catch (BasicException e) { + return ApiResponse.error(e.getResultCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 생성 실패"); } @@ -187,6 +215,7 @@ public ApiResponse createSchedule( "- '일정 생성' API로 받은 응답 DTO를 그대로 보내주세요.") @PostMapping("/save") public ApiResponse saveSchedule( + HttpServletRequest request, @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "일정 등록 요청", required = true, @@ -264,14 +293,26 @@ public ApiResponse saveSchedule( @RequestBody ScheduleRegistResDTO scheduleRegistResDTO ) { - logger.info("CALL /schedule/save"); + logger.info("CALL api/v1/schedule/save"); logger.info("[input] scheduleRegistResDTO={}", scheduleRegistResDTO); int result; try { - //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ - result = scheduleCommandService.saveSchedule(scheduleRegistResDTO); - + // token으로 membershipNo 조회 + Map resultMap = membershipUtil.membershipNoService(request); + String resultCode = String.valueOf(resultMap.get("resultCode")); + if (!"200".equals(resultCode)) { + throw new BasicException( + resultCode, + String.valueOf(resultMap.get("message")) + ); + } + String membershipNo = String.valueOf(resultMap.get("membershipNo")); + + result = scheduleCommandService.saveSchedule(scheduleRegistResDTO, membershipNo); + + } catch (BasicException e) { + return ApiResponse.error(e.getResultCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 저장 실패"); } @@ -360,7 +401,7 @@ public ApiResponse updateSchedule( @RequestBody ScheduleRegistResDTO scheduleRegistResDTO ) { - logger.info("CALL /schedule/update"); + logger.info("CALL /api/v1/schedule/update"); logger.info("[input] scheduleRegistResDTO={}", scheduleRegistResDTO); boolean result; @@ -368,6 +409,8 @@ public ApiResponse updateSchedule( //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ result = scheduleCommandService.updateSchedule(scheduleRegistResDTO); + } catch (BasicException e) { + return ApiResponse.error(e.getResultCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 저장 실패"); } @@ -380,24 +423,22 @@ public ApiResponse updateSchedule( description = "일정 전체를 DB에서 삭제하는 기능입니다.") @DeleteMapping("/") public ApiResponse deleteSchedule(@Parameter(description = "삭제할 일정 번호", example = "1") - @RequestParam Integer scheduleNum) { + @RequestParam Integer scheduleNum, HttpServletRequest request) { - logger.info("CALL /schedule/delete"); + logger.info("CALL /api/v1/schedule/delete"); logger.info("[input] scheduleNum={}", scheduleNum); - boolean result; - try { - //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ - // TODO. membershipNo를 토큰으로부터 받아와야 함 - result = scheduleCommandService.deleteSchedule(scheduleNum, "1"); - - if (!result) return ApiResponse.error("404", "일정 삭제 실패"); - - } catch (Exception e) { - return ApiResponse.error("404", "일정 삭제 실패"); + // token으로 membershipNo 조회 + Map resultMap = membershipUtil.membershipNoService(request); + String resultCode = String.valueOf(resultMap.get("resultCode")); + if (!"200".equals(resultCode)) { + throw new BasicException( resultCode,String.valueOf(resultMap.get("message"))); } + String membershipNo = String.valueOf(resultMap.get("membershipNo")); + // 삭제 처리 + scheduleCommandService.deleteSchedule(scheduleNum, membershipNo); - return ApiResponse.success(result, "일정 삭제 성공"); + return ApiResponse.success(null, "일정 삭제 성공"); } } diff --git a/src/main/java/com/barogagi/schedule/query/mapper/ScheduleMapper.java b/src/main/java/com/barogagi/schedule/query/mapper/ScheduleMapper.java index 16862df..6c8fd84 100644 --- a/src/main/java/com/barogagi/schedule/query/mapper/ScheduleMapper.java +++ b/src/main/java/com/barogagi/schedule/query/mapper/ScheduleMapper.java @@ -3,13 +3,14 @@ import com.barogagi.schedule.dto.ScheduleListResDTO; import com.barogagi.schedule.query.vo.ScheduleDetailVO; import com.barogagi.schedule.query.vo.ScheduleListVO; +import com.barogagi.schedule.query.vo.ScheduleMembershipNoVO; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface ScheduleMapper { - ScheduleDetailVO selectScheduleDetail(int scheduleNum); + ScheduleDetailVO selectScheduleDetail(ScheduleMembershipNoVO scheduleMembershipNoVO); - List selectScheduleList(int membershipNo); + List selectScheduleList(String membershipNo); } diff --git a/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java b/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java index ad817ec..12e2912 100644 --- a/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java +++ b/src/main/java/com/barogagi/schedule/query/service/ScheduleQueryService.java @@ -8,6 +8,9 @@ import com.barogagi.schedule.query.mapper.ScheduleMapper; import com.barogagi.schedule.query.vo.ScheduleDetailVO; import com.barogagi.schedule.query.vo.ScheduleListVO; +import com.barogagi.schedule.query.vo.ScheduleMembershipNoVO; +import com.barogagi.util.exception.BasicException; +import com.barogagi.util.exception.ErrorCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -32,7 +35,7 @@ public ScheduleQueryService (ScheduleMapper scheduleMapper, this.planQueryService = planQueryService; } - public ScheduleListGroupResDTO getScheduleList(int membershipNo) { + public ScheduleListGroupResDTO getScheduleList(String membershipNo) { List scheduleListVOList = scheduleMapper.selectScheduleList(membershipNo); // VO -> DTO 변환 @@ -64,11 +67,18 @@ public ScheduleListGroupResDTO getScheduleList(int membershipNo) { } - public ScheduleDetailResDTO getScheduleDetail(int scheduleNum) throws Exception{ + public ScheduleDetailResDTO getScheduleDetail(int scheduleNum, String membershipNo) { + + logger.info("scheduleNum={}, membershipNo={}", scheduleNum, membershipNo); + // 일정 정보 조회 - ScheduleDetailVO scheduleDetailVO = scheduleMapper.selectScheduleDetail(scheduleNum); + ScheduleMembershipNoVO scheduleMembershipNoVO = new ScheduleMembershipNoVO(scheduleNum, membershipNo); + ScheduleDetailVO scheduleDetailVO = scheduleMapper.selectScheduleDetail(scheduleMembershipNoVO); + if(null == scheduleDetailVO) throw new BasicException(ErrorCode.SCHEDULE_NOT_FOUND); + else if(scheduleDetailVO.getDelYn().equals("Y")) throw new BasicException(ErrorCode.SCHEDULE_ALREADY_DELETED); // 계획 정보 조회 (리스트) + logger.info("계획 조회 시작"); List planDetailVOList = planQueryService.getPlanDetail(scheduleNum); // DTO에 정보 저장 diff --git a/src/main/java/com/barogagi/schedule/query/vo/ScheduleDetailVO.java b/src/main/java/com/barogagi/schedule/query/vo/ScheduleDetailVO.java index baf235a..e5df06e 100644 --- a/src/main/java/com/barogagi/schedule/query/vo/ScheduleDetailVO.java +++ b/src/main/java/com/barogagi/schedule/query/vo/ScheduleDetailVO.java @@ -15,4 +15,5 @@ public class ScheduleDetailVO { public String startDate; // 시작 날짜 public String endDate; // 종료 날짜 public int radius; // 반경 + public String delYn; // 삭제 여부 } diff --git a/src/main/java/com/barogagi/schedule/query/vo/ScheduleMembershipNoVO.java b/src/main/java/com/barogagi/schedule/query/vo/ScheduleMembershipNoVO.java new file mode 100644 index 0000000..f6107cf --- /dev/null +++ b/src/main/java/com/barogagi/schedule/query/vo/ScheduleMembershipNoVO.java @@ -0,0 +1,13 @@ +package com.barogagi.schedule.query.vo; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +@AllArgsConstructor +public class ScheduleMembershipNoVO { + private Integer scheduleNum; + private String membershipNo; +} diff --git a/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java index 5a832b0..1b206af 100644 --- a/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java +++ b/src/main/java/com/barogagi/tag/command/entity/ScheduleTag.java @@ -26,6 +26,10 @@ public class ScheduleTag { @JoinColumn(name = "TAG_NUM") private Tag tag; + @Column(name = "MEMBERSHIP_NO", nullable = false) + private String membershipNo; + + } diff --git a/src/main/resources/mapper/ScheduleMapper.xml b/src/main/resources/mapper/ScheduleMapper.xml index d5f9174..1467d1a 100644 --- a/src/main/resources/mapper/ScheduleMapper.xml +++ b/src/main/resources/mapper/ScheduleMapper.xml @@ -20,13 +20,14 @@ - -
From 55cb0847651912483512c8e974fb276fcaa3c426 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sun, 21 Dec 2025 20:44:31 +0900 Subject: [PATCH 53/79] =?UTF-8?q?feat:=20=EC=97=90=EB=9F=AC=20throw=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/barogagi/plan/query/service/PlanQueryService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java b/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java index 02a3e47..d823d3b 100644 --- a/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java +++ b/src/main/java/com/barogagi/plan/query/service/PlanQueryService.java @@ -19,7 +19,7 @@ public PlanQueryService (PlanMapper planMapper) { this.planMapper = planMapper; } - public List getPlanDetail(int scheduleNum) throws Exception{ + public List getPlanDetail(int scheduleNum){ return planMapper.selectPlanDetailByScheduleNum(scheduleNum); } } From e9280a5a9568a17e67b7374bbfbd14bc9433daa7 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sun, 21 Dec 2025 21:05:30 +0900 Subject: [PATCH 54/79] =?UTF-8?q?feat:=20AI=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=EA=B3=BC=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=9D=BC=EC=A0=95=20=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=EA=B0=92=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../barogagi/plan/dto/PlanRegistResDTO.java | 5 ++ .../com/barogagi/plan/enums/PLAN_SOURCE.java | 7 ++ .../service/ScheduleCommandService.java | 31 ++++---- .../controller/ScheduleController.java | 75 ++++++++++++++++++- 4 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/barogagi/plan/enums/PLAN_SOURCE.java diff --git a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java index 69cf683..d8a35e9 100644 --- a/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java +++ b/src/main/java/com/barogagi/plan/dto/PlanRegistResDTO.java @@ -1,6 +1,7 @@ package com.barogagi.plan.dto; import com.barogagi.kakaoplace.dto.KakaoPlaceResDTO; +import com.barogagi.plan.enums.PLAN_SOURCE; import com.barogagi.region.dto.RegionRegistReqDTO; import com.barogagi.tag.dto.TagRegistReqDTO; import com.barogagi.tag.dto.TagRegistResDTO; @@ -17,6 +18,10 @@ @Schema(description = "계획 등록 응답 DTO") public class PlanRegistResDTO { + + @Schema(description = "계획 타입(USER_PLACE/USER_CUSTOM/AI)", example = "AI") + public PLAN_SOURCE planSource; + @Schema(description = "시작 시간", example = "08:00") public String startTime; diff --git a/src/main/java/com/barogagi/plan/enums/PLAN_SOURCE.java b/src/main/java/com/barogagi/plan/enums/PLAN_SOURCE.java new file mode 100644 index 0000000..29aaa6d --- /dev/null +++ b/src/main/java/com/barogagi/plan/enums/PLAN_SOURCE.java @@ -0,0 +1,7 @@ +package com.barogagi.plan.enums; + +public enum PLAN_SOURCE { + USER_PLACE, // 사용자가 장소 선택 + USER_CUSTOM, // 사용자가 직접 입력 + AI // AI 추천으로 생성 +} \ No newline at end of file diff --git a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java index d04ab29..4f10536 100644 --- a/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java +++ b/src/main/java/com/barogagi/schedule/command/service/ScheduleCommandService.java @@ -17,6 +17,7 @@ import com.barogagi.plan.dto.PlanRegistReqDTO; import com.barogagi.plan.dto.PlanRegistResDTO; import com.barogagi.plan.dto.UserAddedPlaceDTO; +import com.barogagi.plan.enums.PLAN_SOURCE; import com.barogagi.plan.query.mapper.CategoryMapper; import com.barogagi.plan.query.mapper.ItemMapper; import com.barogagi.region.command.entity.Place; @@ -28,7 +29,6 @@ import com.barogagi.region.command.repository.RegionRepository; import com.barogagi.region.dto.RegionGeoCodeResDTO; import com.barogagi.region.dto.RegionRegistReqDTO; -import com.barogagi.region.dto.RegionSearchResDTO; import com.barogagi.region.query.service.RegionGeoCodeService; import com.barogagi.region.query.service.RegionQueryService; import com.barogagi.region.query.vo.RegionDetailVO; @@ -52,7 +52,6 @@ import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDate; import java.util.*; import java.util.stream.Collectors; @@ -143,19 +142,16 @@ public ScheduleRegistResDTO createSchedule(ScheduleRegistReqDTO scheduleRegistRe if (plan.getIsUserAdded().equals("Y")) { // ➜ A. 사용자가 직접 입력한 플랜 - logger.info("#$# ➜ A. 사용자가 직접 입력한 플랜 plan={}", plan); + logger.info("A. 사용자가 직접 입력한 플랜 plan={}", plan); PlanRegistResDTO planRes = handleUserPlan(plan); planResList.add(planRes); } else { // ➜ B. AI가 추천해줘야 하는 플랜 - logger.info("#$# ➜ B. AI가 추천해줘야 하는 플랜 plan={}", plan); + logger.info("B. AI가 추천해줘야 하는 플랜 plan={}", plan); PlanRegistResDTO planRes = handleAIPlan(scheduleRegistReqDTO, plan); planResList.add(planRes); } - logger.info("#$# for문 도는중 planResList={}", planResList); - - } // ---------- 6) ScheduleRegistResDTO 묶어서 리턴 ---------- @@ -170,7 +166,7 @@ public ScheduleRegistResDTO createSchedule(ScheduleRegistReqDTO scheduleRegistRe private PlanRegistResDTO handleAIPlan(ScheduleRegistReqDTO scheduleRegistReqDTO, PlanRegistReqDTO plan) { // ---------- 1) 지역 번호로 x, y 좌표 검색 & Kakao 후보장소 수집(평탄화) ---------- if (plan.getRegionRegistReqDTOList() == null || plan.getRegionRegistReqDTOList().isEmpty()) { - logger.info("#$# skip: plan has no regions. plan={}", plan); + logger.info("skip: plan has no regions. plan={}", plan); return null; } @@ -183,7 +179,7 @@ private PlanRegistResDTO handleAIPlan(ScheduleRegistReqDTO scheduleRegistReqDTO, plan = plan.toBuilder() .categoryNum(categoryMapper.selectRandomCategoryNum()) .build(); - logger.info("#$# getCategoryNum={}", plan.getCategoryNum()); + logger.info("getCategoryNum={}", plan.getCategoryNum()); } String categoryNm = categoryMapper.selectCategoryNmBy(plan.getCategoryNum()); @@ -195,7 +191,7 @@ private PlanRegistResDTO handleAIPlan(ScheduleRegistReqDTO scheduleRegistReqDTO, // regionNum으로 좌표 가져오기 RegionGeoCodeResDTO geo = regionGeoCodeService.getGeocode(region.getRegionNum()); if (geo == null) { - logger.warn("#$# regionNum={} not found in DB, skip.", region.getRegionNum()); + logger.warn("regionNum={} not found in DB, skip.", region.getRegionNum()); continue; } @@ -227,7 +223,7 @@ private PlanRegistResDTO handleAIPlan(ScheduleRegistReqDTO scheduleRegistReqDTO, oneRegionPlaces.forEach(k -> k.setRegionNum(region.getRegionNum())); } - logger.info("#$# resolved regionName={} for regionNum={}", regionName, updatedRegion.getRegionNum()); + logger.info("resolved regionName={} for regionNum={}", regionName, updatedRegion.getRegionNum()); } @@ -241,7 +237,7 @@ private PlanRegistResDTO handleAIPlan(ScheduleRegistReqDTO scheduleRegistReqDTO, .collect(Collectors.toList()); if (flatKakao.isEmpty()) { - logger.info("#$# no kakao results. plan={}", plan); + logger.info("no kakao results. plan={}", plan); return null; } @@ -321,6 +317,7 @@ else if (region.getRegionLevel2() != null && !region.getRegionLevel2().isEmpty() } return PlanRegistResDTO.builder() + .planSource(PLAN_SOURCE.AI) .startTime(plan.getStartTime()) .endTime(plan.getEndTime()) .planNm(aiChosen.getPlaceName()) @@ -352,15 +349,16 @@ private PlanRegistResDTO handleUserPlan(PlanRegistReqDTO plan) { // CASE 1: 카카오 장소 ID로 선택한 경우 if (plan.getUserAddedPlaceDTO() != null) { - logger.info("#$# ➜ A-1. 사용자가 직접 입력한 플랜 plan={}", plan); + logger.info("➜ A-1. 사용자가 직접 입력한 플랜 plan={}", plan); UserAddedPlaceDTO userAddedPlaceDTO = plan.getUserAddedPlaceDTO(); RegionDetailVO regionDetailVO = regionQueryService.getRegionNumByAddress(userAddedPlaceDTO.getAddressName()); String regionNm = resolveRegionName(regionDetailVO); - logger.info("#$# 사용자가 선택한 장소 userAddedPlaceDTO addressName={}, resolved regionNum={}", userAddedPlaceDTO.getAddressName(), regionDetailVO.getRegionNum()); + logger.info("사용자가 선택한 장소 userAddedPlaceDTO addressName={}, resolved regionNum={}", userAddedPlaceDTO.getAddressName(), regionDetailVO.getRegionNum()); return PlanRegistResDTO.builder() + .planSource(PLAN_SOURCE.USER_PLACE) .startTime(plan.getStartTime()) .endTime(plan.getEndTime()) .planNm(userAddedPlaceDTO.getPlaceName()) @@ -378,13 +376,12 @@ private PlanRegistResDTO handleUserPlan(PlanRegistReqDTO plan) { } else { // CASE 2: 텍스트로 직접 입력한 경우 - logger.info("#$# ➜ A-2. 사용자가 직접 입력한 플랜 plan={}", plan); + logger.info("➜ A-2. 사용자가 직접 입력한 플랜 plan={}", plan); - logger.info("#$# plan.getRegionRegistReqDTOList().get(0).getRegionNum()={}", plan.getRegionRegistReqDTOList().get(0).getRegionNum()); RegionDetailVO region = regionQueryService.getRegionByRegionNum(plan.getRegionRegistReqDTOList().get(0).getRegionNum()); - logger.info("#$# regionNm={} {}", region.getRegionLevel2(), region.getRegionLevel3()); return PlanRegistResDTO.builder() + .planSource(PLAN_SOURCE.USER_CUSTOM) .startTime(plan.getStartTime()) .endTime(plan.getEndTime()) .planNm(plan.getPlanNm()) diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index a4154a1..129ad0a 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -221,8 +221,9 @@ public ApiResponse saveSchedule( required = true, content = @Content( mediaType = "application/json", - examples = @ExampleObject( - name = "일정 등록 요청 예시", + examples = { + @ExampleObject( + name = "AI 추천 일정만 저장 요청 예시", value = "{\n" + " \"scheduleNum\": null,\n" + " \"scheduleNm\": \"서울 데이트 코스\",\n" + @@ -234,6 +235,7 @@ public ApiResponse saveSchedule( " ],\n" + " \"planRegistResDTOList\": [\n" + " {\n" + + " \"planSource\": \"AI\",\n" + " \"startTime\": \"08:30\",\n" + " \"endTime\": \"09:00\",\n" + " \"itemNum\": 10,\n" + @@ -252,6 +254,7 @@ public ApiResponse saveSchedule( " ]\n" + " },\n" + " {\n" + + " \"planSource\": \"AI\",\n" + " \"startTime\": \"14:00\",\n" + " \"endTime\": \"15:00\",\n" + " \"itemNum\": 2,\n" + @@ -269,6 +272,7 @@ public ApiResponse saveSchedule( " ]\n" + " },\n" + " {\n" + + " \"planSource\": \"AI\",\n" + " \"startTime\": \"15:30\",\n" + " \"endTime\": \"19:00\",\n" + " \"itemNum\": 15,\n" + @@ -287,7 +291,74 @@ public ApiResponse saveSchedule( " }\n" + " ]\n" + "}" + ), + @ExampleObject( + name = "AI 추천 + 사용자 추가 일정 저장 요청 예시", + value = "{\n" + + " \"scheduleNum\": null,\n" + + " \"scheduleNm\": \"3월 여행 일정\",\n" + + " \"startDate\": \"2026-03-11\",\n" + + " \"endDate\": \"2026-03-11\",\n" + + " \"scheduleTagRegistResDTOList\": [\n" + + " { \"tagNm\": \"즐거운\", \"tagNum\": 6 },\n" + + " { \"tagNm\": \"저렴한\", \"tagNum\": 4 }\n" + + " ],\n" + + " \"planRegistResDTOList\": [\n" + + " {\n" + + " \"planSource\": \"AI\",\n" + + " \"startTime\": \"08:30\",\n" + + " \"endTime\": \"09:00\",\n" + + " \"itemNum\": 10,\n" + + " \"itemNm\": \"프랜차이즈카페\",\n" + + " \"categoryNum\": 2,\n" + + " \"categoryNm\": \"카페\",\n" + + " \"planNm\": \"부빙\",\n" + + " \"planLink\": \"http://place.map.kakao.com/20459372\",\n" + + " \"planDescription\": \"'부빙'은 계절마다 변하는 감성 빙수를 판매하는 디저트 카페입니다.\",\n" + + " \"planAddress\": \"서울 종로구 창의문로 136\",\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": [\n" + + " { \"tagNum\": 14, \"tagNm\": \"디저트맛집\" },\n" + + " { \"tagNum\": 15, \"tagNm\": \"인스타핫플\" }\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"planSource\": \"USER_PLACE\",\n" + + " \"startTime\": \"14:00\",\n" + + " \"endTime\": \"15:00\",\n" + + " \"itemNum\": 2,\n" + + " \"itemNm\": \"한식\",\n" + + " \"categoryNum\": 1,\n" + + " \"categoryNm\": \"식사\",\n" + + " \"planNm\": \"카카오프렌즈 코엑스점\",\n" + + " \"planLink\": \"http://place.map.kakao.com/26338954\",\n" + + " \"planDescription\": null,\n" + + " \"planAddress\": \"서울 강남구 삼성동 159\",\n" + + " \"regionNm\": \"강남구\",\n" + + " \"regionNum\": 9,\n" + + " \"planTagRegistResDTOList\": []\n" + + " },\n" + + " {\n" + + " \"planSource\": \"USER_CUSTOM\",\n" + + " \"startTime\": \"15:30\",\n" + + " \"endTime\": \"19:00\",\n" + + " \"itemNum\": 15,\n" + + " \"itemNm\": \"놀이공원\",\n" + + " \"categoryNum\": 4,\n" + + " \"categoryNm\": \"놀거리\",\n" + + " \"planNm\": \"친구집 방문\",\n" + + " \"planLink\": null,\n" + + " \"planDescription\": null,\n" + + " \"planAddress\": null,\n" + + " \"regionNm\": \"종로구\",\n" + + " \"regionNum\": 1,\n" + + " \"planTagRegistResDTOList\": []\n" + + " }\n" + + " ]\n" + + "}" ) + } ) ) @RequestBody ScheduleRegistResDTO scheduleRegistResDTO From 59dff930ffeac65df949fd5f327e1fa04514b496 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 22 Dec 2025 11:49:58 +0900 Subject: [PATCH 55/79] =?UTF-8?q?FEAT=20:=20=EB=A1=9C=EA=B7=B8=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=ED=99=94=20&=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20(#39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] : 로그 공통화 작업중 & 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 일반 로그인 & 토큰 재발급 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 약관 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 회원정보 코드 리팩토링 중 * [FEAT] : 로그 공통화 작업중 & 회원정보 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 메인 화면 api 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 인증 api 코드 리팩토링 * [FEAT] : 필요없는 import 삭제 * [FEAT] : 필요없는 import 삭제 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 --- .../controller/ApprovalController.java | 171 +--------- .../approval/exception/ApprovalException.java | 12 + .../approval/service/ApprovalService.java | 138 +++++++- .../com/barogagi/config/JwtAuthFilter.java | 44 +-- .../config/OAuth2LoginSuccessHandler.java | 2 + .../com/barogagi/config/SecurityConfig.java | 12 +- .../config/exception/BusinessException.java | 14 + .../config/resultCode/ProcessResultCode.java | 82 +++++ .../config/resultCode/ResultCode.java | 23 ++ .../logging/ControllerLoggingAspect.java | 27 ++ .../logging/ServiceLoggingAspect.java | 44 +++ .../controller/MainPageController.java | 172 +--------- .../mainPage/exception/MainPageException.java | 8 +- .../mainPage/response/MainPageResponse.java | 20 ++ .../mainPage/service/MainPageService.java | 127 ++++++- .../basic/join/controller/JoinController.java | 317 ++---------------- .../basic/join/exception/JoinException.java | 12 + .../member/basic/join/mapper/JoinMapper.java | 4 +- .../basic/join/service/BasicJoinService.java | 285 ++++++++++++++++ .../basic/join/service/JoinService.java | 102 +++--- .../info/controller/InfoController.java | 158 +-------- .../info/exception/MemberInfoException.java | 7 +- .../member/info/service/MemberService.java | 137 +++++++- .../login/controller/AuthController.java | 36 -- .../login/controller/LoginController.java | 280 +--------------- .../login/dto/RefreshTokenRequestDTO.java | 12 + .../barogagi/member/login/dto/TokenPair.java | 3 +- .../InvalidRefreshTokenException.java | 10 +- .../login/exception/LoginException.java | 9 +- .../member/login/service/AuthService.java | 120 ++++--- .../member/login/service/LoginService.java | 243 +++++++++++++- .../com/barogagi/response/ApiResponse.java | 15 + .../terms/controller/TermsController.java | 136 +------- .../terms/exception/TermsException.java | 12 + .../barogagi/terms/service/TermsService.java | 127 ++++++- .../com/barogagi/util/MembershipUtil.java | 23 +- .../java/com/barogagi/util/Validator.java | 14 + .../util/exception/BasicException.java | 7 +- .../exception/GlobalExceptionHandler.java | 6 + src/main/resources/mapper/JoinMapper.xml | 4 +- 40 files changed, 1582 insertions(+), 1393 deletions(-) create mode 100644 src/main/java/com/barogagi/approval/exception/ApprovalException.java create mode 100644 src/main/java/com/barogagi/config/exception/BusinessException.java create mode 100644 src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java create mode 100644 src/main/java/com/barogagi/config/resultCode/ResultCode.java create mode 100644 src/main/java/com/barogagi/logging/ControllerLoggingAspect.java create mode 100644 src/main/java/com/barogagi/logging/ServiceLoggingAspect.java create mode 100644 src/main/java/com/barogagi/member/basic/join/exception/JoinException.java create mode 100644 src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java create mode 100644 src/main/java/com/barogagi/member/login/dto/RefreshTokenRequestDTO.java create mode 100644 src/main/java/com/barogagi/terms/exception/TermsException.java diff --git a/src/main/java/com/barogagi/approval/controller/ApprovalController.java b/src/main/java/com/barogagi/approval/controller/ApprovalController.java index d440b4e..602e481 100644 --- a/src/main/java/com/barogagi/approval/controller/ApprovalController.java +++ b/src/main/java/com/barogagi/approval/controller/ApprovalController.java @@ -1,54 +1,29 @@ package com.barogagi.approval.controller; import com.barogagi.approval.service.ApprovalService; -import com.barogagi.approval.service.AuthCodeService; import com.barogagi.approval.vo.ApprovalCompleteVO; import com.barogagi.approval.vo.ApprovalSendVO; -import com.barogagi.approval.vo.ApprovalVO; import com.barogagi.response.ApiResponse; -import com.barogagi.sendSms.dto.SendSmsVO; -import com.barogagi.sendSms.service.SendSmsService; -import com.barogagi.util.EncryptUtil; -import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; @Tag(name = "인증", description = "인증 API") @RestController @RequestMapping("/api/v1/verification-codes") public class ApprovalController { - private static final Logger logger = LoggerFactory.getLogger(ApprovalController.class); - @Autowired - private InputValidate inputValidate; - - @Autowired - private EncryptUtil encryptUtil; - - @Autowired - private AuthCodeService authCodeService; - - @Autowired - private ApprovalService approvalService; - - @Autowired - private SendSmsService sendSmsService; - - private final String API_SECRET_KEY; + private final ApprovalService approvalService; @Autowired - public ApprovalController(Environment environment){ - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + public ApprovalController(ApprovalService approvalService){ + this.approvalService = approvalService; } @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.
회원가입 시 사용할 경우 type 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "인증번호를 발송할 전화번호를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증번호 발송에 성공하었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "오류가 발생하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "인증번호 발송에 실패하였습니다."), @@ -57,86 +32,7 @@ public ApprovalController(Environment environment){ }) @PostMapping("/send") public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { - - logger.info("CALL /api/v1/verification-codes/send"); - logger.info("[input] API_SECRET_KEY={}", approvalSendVO.getApiSecretKey()); - - ApprovalVO approvalVO = new ApprovalVO(); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(approvalSendVO.getApiSecretKey().equals(API_SECRET_KEY)) { - - if(inputValidate.isEmpty(approvalSendVO.getTel())){ - resultCode = "101"; - message = "인증번호를 발송할 전화번호를 입력해주세요."; - - } else{ - - // 전화번호 - String recipientTel = approvalSendVO.getTel(); - - // 인증번호를 DB에 INSERT 전에, 전에 발송된 기록들은 flag UPDATE 처리 - approvalVO.setCompleteYn("N"); - approvalVO.setType(approvalSendVO.getType()); - - // 전화번호 암호화 - approvalVO.setTel(encryptUtil.hashEncodeString(approvalSendVO.getTel())); - - int updateResult = approvalService.updateApprovalRecord(approvalVO); - logger.info("@@ updateResult={}", updateResult); - - // 인증번호 생성 - String authCode = authCodeService.generateAuthCode(); - logger.info("@@ authCode={}", authCode); - - // 인증번호 메시지 발송 - SendSmsVO sendSmsVO = new SendSmsVO(); - sendSmsVO.setRecipientTel(recipientTel); - String messageContent = "인증번호는 [" + authCode + "] 입니다."; - sendSmsVO.setMessageContent(messageContent); - boolean sendMessageResult = sendSmsService.sendSms(sendSmsVO); - logger.info("@@ sendMessageResult={}", sendMessageResult); - - // 인증번호 암호화 - approvalVO.setAuthCode(encryptUtil.hashEncodeString(authCode)); - - // 인증번호를 DB에 insert - if(sendMessageResult){ - approvalVO.setMessageContent(sendSmsVO.getMessageContent()); - int insertResult = approvalService.insertApprovalRecord(approvalVO); - logger.info("@@ insertResult={}", insertResult); - if(insertResult > 0) { - // 인증번호 발송 로직 - resultCode = "200"; - message = "인증번호 발송에 성공하었습니다."; - - } else{ - resultCode = "102"; - message = "오류가 발생하였습니다."; - } - } else { - resultCode = "103"; - message = "인증번호 발송에 실패하였습니다."; - } - } - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return approvalService.approvalTelSend(approvalSendVO); } @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인." + @@ -144,7 +40,7 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { "
authCode에는 인증번호를 넣어주세요." + "
tel에는 전화번호를 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호 또는 인증번호 값을 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증이 완료되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "인증이 실패하었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @@ -152,59 +48,6 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { }) @PostMapping("/verify") public ApiResponse approvalTelCheck(@RequestBody ApprovalCompleteVO approvalCompleteVO) { - - logger.info("CALL /api/v1/verification-codes/verify"); - logger.info("[input] API_SECRET_KEY={}", approvalCompleteVO.getApiSecretKey()); - - ApprovalVO approvalVO = new ApprovalVO(); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(approvalCompleteVO.getApiSecretKey().equals(API_SECRET_KEY)) { - if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode()) || inputValidate.isEmpty(approvalCompleteVO.getTel())){ - resultCode = "101"; - message = "전화번호 또는 인증번호 값을 입력해주세요."; - - } else{ - logger.info("@@@@ authCode = {}", approvalCompleteVO.getAuthCode()); - - // 전화번호 암호화 - approvalVO.setTel(encryptUtil.hashEncodeString(approvalCompleteVO.getTel())); - approvalVO.setCompleteYn("N"); - approvalVO.setAuthCode(encryptUtil.hashEncodeString(approvalCompleteVO.getAuthCode())); - approvalVO.setType(approvalCompleteVO.getType()); - - logger.info("authcode = {}", encryptUtil.hashEncodeString(approvalCompleteVO.getAuthCode())); - - int updateResult = approvalService.updateApprovalComplete(approvalVO); - logger.info("@@ updateResult={}", updateResult); - - if(updateResult == 1){ - resultCode = "200"; - message = "인증이 완료되었습니다."; - } else { - resultCode = "300"; - message = "인증에 실패하였습니다."; - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return approvalService.approvalTelCheck(approvalCompleteVO); } } diff --git a/src/main/java/com/barogagi/approval/exception/ApprovalException.java b/src/main/java/com/barogagi/approval/exception/ApprovalException.java new file mode 100644 index 0000000..5dde8e6 --- /dev/null +++ b/src/main/java/com/barogagi/approval/exception/ApprovalException.java @@ -0,0 +1,12 @@ +package com.barogagi.approval.exception; + +import com.barogagi.config.exception.BusinessException; +import lombok.Getter; + +@Getter +public class ApprovalException extends BusinessException { + + public ApprovalException(String resultCode, String message) { + super(resultCode, message); + } +} diff --git a/src/main/java/com/barogagi/approval/service/ApprovalService.java b/src/main/java/com/barogagi/approval/service/ApprovalService.java index 661a2ec..6cb6f64 100644 --- a/src/main/java/com/barogagi/approval/service/ApprovalService.java +++ b/src/main/java/com/barogagi/approval/service/ApprovalService.java @@ -1,18 +1,154 @@ package com.barogagi.approval.service; +import com.barogagi.approval.exception.ApprovalException; import com.barogagi.approval.mapper.ApprovalMapper; +import com.barogagi.approval.vo.ApprovalCompleteVO; +import com.barogagi.approval.vo.ApprovalSendVO; import com.barogagi.approval.vo.ApprovalVO; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.response.ApiResponse; +import com.barogagi.sendSms.dto.SendSmsVO; +import com.barogagi.sendSms.service.SendSmsService; +import com.barogagi.util.EncryptUtil; +import com.barogagi.util.InputValidate; +import com.barogagi.util.Validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestBody; @Service public class ApprovalService { private final ApprovalMapper approvalMapper; + private final Validator validator; + private final InputValidate inputValidate; + private final EncryptUtil encryptUtil; + private final AuthCodeService authCodeService; + private final SendSmsService sendSmsService; @Autowired - public ApprovalService(ApprovalMapper approvalMapper){ + public ApprovalService( + ApprovalMapper approvalMapper, + Validator validator, + InputValidate inputValidate, + EncryptUtil encryptUtil, + AuthCodeService authCodeService, + SendSmsService sendSmsService + ) + { this.approvalMapper = approvalMapper; + this.validator = validator; + this.inputValidate = inputValidate; + this.encryptUtil = encryptUtil; + this.authCodeService = authCodeService; + this.sendSmsService = sendSmsService; + } + + public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(approvalSendVO.getApiSecretKey())) { + throw new ApprovalException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(approvalSendVO.getTel())) { + throw new ApprovalException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 처리 + // 인증번호를 DB에 INSERT 전에, 전에 발송된 기록들은 flag UPDATE 처리 + ApprovalVO approvalVO = new ApprovalVO(); + approvalVO.setCompleteYn("N"); + approvalVO.setType(approvalSendVO.getType()); + + // 전화번호 암호화 + approvalVO.setTel(encryptUtil.hashEncodeString(approvalSendVO.getTel())); + + int updateResult = this.updateApprovalRecord(approvalVO); + + // 인증번호 생성 + String authCode = authCodeService.generateAuthCode(); + + // 인증번호 메시지 발송 + SendSmsVO sendSmsVO = new SendSmsVO(); + sendSmsVO.setRecipientTel(approvalSendVO.getTel()); + String messageContent = "인증번호는 [" + authCode + "] 입니다."; + sendSmsVO.setMessageContent(messageContent); + boolean sendMessageResult = sendSmsService.sendSms(sendSmsVO); + + if(!sendMessageResult) { + throw new ApprovalException( + ProcessResultCode.FAIL_SEND_SMS.getResultCode(), + ProcessResultCode.FAIL_SEND_SMS.getMessage() + ); + } + + // 인증번호 암호화 + approvalVO.setAuthCode(encryptUtil.hashEncodeString(authCode)); + + approvalVO.setMessageContent(sendSmsVO.getMessageContent()); + int insertResult = this.insertApprovalRecord(approvalVO); + + if(insertResult <= 0) { + throw new ApprovalException( + ProcessResultCode.ERROR_SEND_SMS.getResultCode(), + ProcessResultCode.ERROR_SEND_SMS.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_SEND_SMS.getResultCode(), + ProcessResultCode.SUCCESS_SEND_SMS.getMessage() + ); + } + + public ApiResponse approvalTelCheck(ApprovalCompleteVO approvalCompleteVO) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(approvalCompleteVO.getApiSecretKey())) { + throw new ApprovalException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode()) + || inputValidate.isEmpty(approvalCompleteVO.getTel())) { + throw new ApprovalException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 전화번호 암호화 + ApprovalVO approvalVO = new ApprovalVO(); + approvalVO.setTel(encryptUtil.hashEncodeString(approvalCompleteVO.getTel())); + approvalVO.setCompleteYn("N"); + approvalVO.setAuthCode(encryptUtil.hashEncodeString(approvalCompleteVO.getAuthCode())); + approvalVO.setType(approvalCompleteVO.getType()); + + // 4. 인증 + int updateResult = this.updateApprovalComplete(approvalVO); + if(updateResult != 1){ + throw new ApprovalException( + ProcessResultCode.FAIL_CHECK_SMS.getResultCode(), + ProcessResultCode.FAIL_CHECK_SMS.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_CHECK_SMS.getResultCode(), + ProcessResultCode.SUCCESS_CHECK_SMS.getMessage() + ); } public int updateApprovalRecord(ApprovalVO vo){ diff --git a/src/main/java/com/barogagi/config/JwtAuthFilter.java b/src/main/java/com/barogagi/config/JwtAuthFilter.java index e1b1fec..40c4f70 100644 --- a/src/main/java/com/barogagi/config/JwtAuthFilter.java +++ b/src/main/java/com/barogagi/config/JwtAuthFilter.java @@ -2,33 +2,29 @@ import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.service.MemberService; -import com.barogagi.member.login.repository.UserMembershipRepository; +import com.barogagi.member.login.exception.InvalidRefreshTokenException; import com.barogagi.util.JwtUtil; +import com.barogagi.config.resultCode.ResultCode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.*; import jakarta.servlet.http.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; + import java.io.IOException; @Component public class JwtAuthFilter extends OncePerRequestFilter { - Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class); - private final JwtUtil jwt; - private final UserMembershipRepository userRepo; private final MemberService memberService; - public JwtAuthFilter(JwtUtil jwt, UserMembershipRepository userRepo, MemberService memberService) { + public JwtAuthFilter(JwtUtil jwt, MemberService memberService) { this.jwt = jwt; - this.userRepo = userRepo; this.memberService = memberService; } @@ -38,16 +34,11 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, try { String header = req.getHeader("Authorization"); - - logger.info("@@@ header={}", header); - if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); - logger.info("@@@ token={}", token); Claims claims = jwt.parseToken(token, "ACCESS"); String membershipNo = jwt.getMembershipNo(claims); - logger.info("@@@ membershipNo={}", membershipNo); // 회원 조회 Member member = memberService.findByMembershipNo(membershipNo); @@ -62,12 +53,21 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, chain.doFilter(req, res); } catch (ExpiredJwtException e) { // 유효기간이 지나서 만료된 경우 - writeErrorResponse(res, "300", "Access token has expired"); + writeErrorResponse( + ResultCode.EXPIRE_TOKEN.getResultCode(), + ResultCode.EXPIRE_TOKEN.getMessage() + ); } catch (JwtException | SecurityException e) { // 위조되었거나 변조되었거나 구조가 잘못되었을 경우 - writeErrorResponse(res, "301", "Revoked access token"); + writeErrorResponse( + ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), + ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() + ); } catch (Exception e) { - writeErrorResponse(res, "302", "Unknown authentication error"); + writeErrorResponse( + ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), + ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() + ); } } @@ -78,16 +78,8 @@ protected boolean shouldNotFilter(HttpServletRequest request) { return p.startsWith("/auth/") || p.startsWith("/login/basic/membership/userId/search"); } - private void writeErrorResponse(HttpServletResponse res, String errorCode, String message) throws IOException { - res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - res.setContentType("application/json;charset=UTF-8"); - - String json = String.format( - "{\"errorCode\":\"%s\", \"message\":\"%s\"}", - errorCode, message - ); - - res.getWriter().write(json); + private void writeErrorResponse(String resultCode, String message) throws IOException { + throw new InvalidRefreshTokenException(resultCode, message); } } diff --git a/src/main/java/com/barogagi/config/OAuth2LoginSuccessHandler.java b/src/main/java/com/barogagi/config/OAuth2LoginSuccessHandler.java index 12a90f3..f2a30c2 100644 --- a/src/main/java/com/barogagi/config/OAuth2LoginSuccessHandler.java +++ b/src/main/java/com/barogagi/config/OAuth2LoginSuccessHandler.java @@ -42,6 +42,8 @@ public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res.setContentType("application/json;charset=UTF-8"); objectMapper.writeValue(res.getWriter(), Map.of( + "resultCode", login.tokens().resultCode(), + "message", login.tokens().message(), "accessToken", login.tokens().accessToken(), "accessTokenExpiresIn", login.tokens().accessTokenExpiresIn(), "userId", userId, diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index b07721a..b8952a7 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -2,6 +2,7 @@ import com.barogagi.member.oauth.join.service.CustomOidcUserService; import com.barogagi.member.oauth.join.service.DelegatingOAuth2UserService; +import com.barogagi.config.resultCode.ResultCode; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +44,7 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, "/api/v1/terms/**", // 약관 관련 "/api/v1/home/tags/popular", // 인기 태그 조회 "/api/v1/home/regions/popular", // 인기 지역 조회 + "/api/v1/verification-codes/**" }; @Bean @@ -74,9 +76,17 @@ SecurityFilterChain filterChain( .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) // 브라우저 리다이렉트 대신 401 JSON .exceptionHandling(ex -> ex.authenticationEntryPoint((req, res, e) -> { + + String resultCode = ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(); + String message = ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage(); + res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.setContentType("application/json;charset=UTF-8"); - res.getWriter().write("{\"error\":\"unauthorized\"}"); + String json = String.format( + "{\"resultCode\":\"%s\", \"message\":\"%s\"}", + resultCode, message + ); + res.getWriter().write(json); })); return http.build(); diff --git a/src/main/java/com/barogagi/config/exception/BusinessException.java b/src/main/java/com/barogagi/config/exception/BusinessException.java new file mode 100644 index 0000000..a87dcdf --- /dev/null +++ b/src/main/java/com/barogagi/config/exception/BusinessException.java @@ -0,0 +1,14 @@ +package com.barogagi.config.exception; + +import lombok.Getter; + +@Getter +public abstract class BusinessException extends RuntimeException { + + private final String resultCode; + + public BusinessException(String resultCode, String message) { + super(message); + this.resultCode = resultCode; + } +} diff --git a/src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java b/src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java new file mode 100644 index 0000000..a2d9f18 --- /dev/null +++ b/src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java @@ -0,0 +1,82 @@ +package com.barogagi.config.resultCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ProcessResultCode { + + EMPTY_DATA("101", "정보를 입력해주세요."), + + // nickname + INVALID_NICKNAME("102", "적합하지 않는 닉네임입니다."), + UNAVAILABLE_NICKNAME("103", "해당 닉네임 사용이 불가능합니다."), + AVAILABLE_NICKNAME("200", "사용 가능한 닉네임입니다."), + + // userId + INVALID_USER_ID("102", "적합한 아이디가 아닙니다."), + UNAVAILABLE_USER_ID("300", "해당 아이디 사용이 불가능합니다."), + AVAILABLE_USER_ID("200", "해당 아이디 사용이 가능합니다."), + + // signUp + INVALID_SIGN_UP("102", "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), + SUCCESS_SIGN_UP("200", "회원가입에 성공하였습니다."), + FAIL_SIGN_UP("300", "회원가입에 실패하였습니다."), + + // deleteAccount + SUCCESS_DELETE_ACCOUNT("200", "회원 탈퇴되었습니다."), + FAIL_DELETE_ACCOUNT("300", "회원 탈퇴 실패하였습니다."), + + // findUser + FOUND_ACCOUNT("200", "해당 전화번호로 가입된 아이디가 존재합니다."), + NOT_FOUND_ACCOUNT("201", "해당 전화번호로 가입된 계정이 존재하지 않습니다."), + + // updatePassword + SUCCESS_UPDATE_PASSWORD("200", "비밀번호 재설정에 성공하였습니다."), + FAIL_UPDATE_PASSWORD("300", "비밀번호 재설정에 실패하였습니다."), + + // Login + NOT_FOUND_USER_INFO("102", "회원 정보가 존재하지 않습니다."), + FAIL_LOGIN("103", "로그인에 실패하였습니다."), + SUCCESS_LOGIN("200", "로그인에 성공하였습니다."), + + // refreshToken + REQUIRED_LOGIN("110", "로그인을 진행해주세요."), + REQUIRED_RE_LOGIN("120", "로그인을 다시 진행해주세요."), + SUCCESS_REFRESH_TOKEN("200", "토큰이 발급되었습니다."), + FAIL_REFRESH_TOKEN("130", "토큰 발급에 실패하였습니다."), + + // logout + FAIL_LOGOUT("300", "로그아웃 실패하였습니다."), + SUCCESS_LOGOUT("200", "로그아웃 되었습니다."), + + // terms + FOUND_TERMS("200", "약관 조회에 성공하였습니다."), + NOT_FOUND_TERMS("102", "약관이 존재하지 않습니다."), + SUCCESS_INSERT_TERMS("200", "약관 저장에 성공하였습니다."), + FAIL_INSERT_TERMS("300", "약관 저장에 실패하였습니다."), + + // memberInfo + FOUND_USER_INFO("200", "회원 정보 조회가 완료되었습니다."), + FAIL_UPDATE_USER_INFO("404", "사용자 정보 수정 실패하였습니다."), + SUCCESS_UPDATE_USER_INFO("200", "사용자 정보 수정 완료하였습니다."), + + // mainPage + NOT_FOUND_SCHEDULE("201", "일정이 존재하지 않습니다."), + FOUND_SCHEDULE("200", "조회 성공하였습니다."), + NOT_FOUND_POPULAR_TAG("201", "인기 태그 목록이 존재하지 않습니다."), + FOUND_POPULAR_TAG("200", "인기 태그 조회 완료하였습니다."), + NOT_FOUND_POPULAR_REGION("201", "인기 지역 목록이 존재하지 않습니다."), + FOUND_POPULAR_REGION("200", "인기 지역 조회 완료하였습니다."), + + // approval + SUCCESS_SEND_SMS("200", "인증번호 발송에 성공하었습니다."), + FAIL_SEND_SMS("103", "인증번호 발송에 실패하였습니다."), + ERROR_SEND_SMS("102", "오류가 발생하였습니다."), + SUCCESS_CHECK_SMS("200", "인증이 완료되었습니다."), + FAIL_CHECK_SMS("300", "인증에 실패하였습니다."); + + private final String resultCode; + private final String message; +} diff --git a/src/main/java/com/barogagi/config/resultCode/ResultCode.java b/src/main/java/com/barogagi/config/resultCode/ResultCode.java new file mode 100644 index 0000000..a854ce5 --- /dev/null +++ b/src/main/java/com/barogagi/config/resultCode/ResultCode.java @@ -0,0 +1,23 @@ +package com.barogagi.config.resultCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ResultCode { + + // API_SECRET_KEY 일치 X + NOT_EQUAL_API_SECRET_KEY("100", "잘못된 접근입니다."), + + // ACCESS TOKEN + NOT_EXIST_ACCESS_AUTH("401", "접근 권한이 존재하지 않습니다."), + EXIST_ACCESS_AUTH("200", "회원 번호가 존재합니다."), + EXPIRE_TOKEN("300", "Token이 만료되었습니다."), + + // 서버 오류 + ERROR("400","오류가 발생하였습니다."); + + private final String resultCode; + private final String message; +} diff --git a/src/main/java/com/barogagi/logging/ControllerLoggingAspect.java b/src/main/java/com/barogagi/logging/ControllerLoggingAspect.java new file mode 100644 index 0000000..240551b --- /dev/null +++ b/src/main/java/com/barogagi/logging/ControllerLoggingAspect.java @@ -0,0 +1,27 @@ +package com.barogagi.logging; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Slf4j +@Aspect +@Component +public class ControllerLoggingAspect { + + @Around("@within(org.springframework.web.bind.annotation.RestController)") + public Object logController(ProceedingJoinPoint joinPoint) throws Throwable { + + String className = joinPoint.getTarget().getClass().getSimpleName(); + String methodName = joinPoint.getSignature().getName(); + + log.info("Controller Start - {}.{}", className, methodName); + + Object result = joinPoint.proceed(); + log.info("Controller End - {}.{}", className, methodName); + + return result; + } +} diff --git a/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java b/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java new file mode 100644 index 0000000..0e43bf7 --- /dev/null +++ b/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java @@ -0,0 +1,44 @@ +package com.barogagi.logging; + +import com.barogagi.config.exception.BusinessException; +import com.barogagi.response.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Slf4j +@Aspect +@Component +public class ServiceLoggingAspect { + + @Around("execution(* com.barogagi..service..*(..))") + public Object logService(ProceedingJoinPoint joinPoint) throws Throwable { + + String className = joinPoint.getTarget().getClass().getSimpleName(); + String methodName = joinPoint.getSignature().getName(); + + long startTime = System.currentTimeMillis(); + + log.info("Service Start - {}.{}", className, methodName); + + try { + Object result = joinPoint.proceed(); + + long elapsedTime = System.currentTimeMillis() - startTime; + log.info("Service End - {}.{}, time={}ms", + className, methodName, elapsedTime); + + return result; + } catch (BusinessException ex) { + log.error("Service BusinessException - {}.{} / resultCode - {} / message - {}", className, methodName, ex.getResultCode(), ex.getMessage(), ex); + throw ex; + + } catch (Exception e) { + log.error("Service Exception - {}.{}", className, methodName, e); + throw e; + } + } +} + diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index c7919f1..a7db4fb 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -1,43 +1,24 @@ package com.barogagi.mainPage.controller; -import com.barogagi.mainPage.dto.*; -import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.mainPage.response.MainPageResponse; import com.barogagi.mainPage.service.MainPageService; import com.barogagi.response.ApiResponse; -import com.barogagi.util.MembershipUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.Map; - @Tag(name = "메인 화면", description = "메인 화면에 필요한 API") @RestController @RequestMapping("/api/v1/home") public class MainPageController { - private static final Logger logger = LoggerFactory.getLogger(MainPageController.class); - private final MainPageService mainPageService; - private final MembershipUtil membershipUtil; - - private final String API_SECRET_KEY; - @Autowired - public MainPageController(MainPageService mainPageService, - MembershipUtil membershipUtil, - Environment environment) { + public MainPageController(MainPageService mainPageService) { this.mainPageService = mainPageService; - this.membershipUtil = membershipUtil; - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } @Operation(summary = "유저 일정 정보 API", description = "메인 화면 - 다가오는 일정 부분에 해당하는 API", @@ -49,75 +30,7 @@ public MainPageController(MainPageService mainPageService, }) @GetMapping("/me/schedules") public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { - - logger.info("CALL /api/v1/home/me/schedules"); - - MainPageResponse mainPageResponse = new MainPageResponse(); - String resultCode = ""; - String message = ""; - - try { - - // 회원번호 구하기 - Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MainPageException(String.valueOf(membershipNoInfo.get("resultCode")), - String.valueOf(membershipNoInfo.get("message"))); - } - - String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); - - // 유저 일정 정보 API - UserInfoRequestDTO userInfoRequestDTO = new UserInfoRequestDTO(); - userInfoRequestDTO.setMembershipNo(membershipNo); - - UserInfoResponseDTO userInfoResponseDTO = mainPageService.selectUserScheduleInfo(userInfoRequestDTO); - - if(null == userInfoResponseDTO) { - resultCode = "201"; - message = "일정이 존재하지 않습니다."; - - } else { - - resultCode = "200"; - message = "조회 성공하였습니다."; - - mainPageResponse.setUserInfoResponseDTO(userInfoResponseDTO); - - // 일정 번호 - userInfoRequestDTO.setScheduleNum(userInfoResponseDTO.getScheduleNum()); - - // 해당 schedule에 대한 태그 목록 조회 - List tagList = mainPageService.selectScheduleTag(userInfoRequestDTO); - - if(!tagList.isEmpty()) { - mainPageResponse.setTagInfoList(tagList); - } - - // 해당 plan에 대한 region 정보 조회 - userInfoRequestDTO.setPlanNum(userInfoResponseDTO.getPlanNum()); - - RegionInfoDTO regionInfo = mainPageService.selectScheduleRegionInfo(userInfoRequestDTO); - - if(null != regionInfo) { - mainPageResponse.setRegionInfoDTO(regionInfo); - } - } - - } catch (MainPageException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - mainPageResponse.setResultCode(resultCode); - mainPageResponse.setMessage(message); - } - - return mainPageResponse; + return mainPageService.selectUserScheduleInfoProcess(request); } @Operation(summary = "인기 태그 조회 API ", description = "메인 화면 - 오늘 많이 생성되는 일정 부분에 해당하는 API", @@ -129,46 +42,7 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { }) @GetMapping("/tags/popular") public ApiResponse selectPopularTagList(@RequestHeader("API-KEY") String apiSecretKey) { - - logger.info("CALL /api/v1/home/tags/popular"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(!apiSecretKey.equals(API_SECRET_KEY)) { - throw new MainPageException("100", "잘못된 접근입니다."); - } - - // 인기 태그 조회 - List tagRankInfoList = mainPageService.selectTagRankList(); - - logger.info("@@ !tagRankInfoList.isEmpty()={}", !tagRankInfoList.isEmpty()); - if(!tagRankInfoList.isEmpty()) { - resultCode = "200"; - message = "인기 태그 조회 완료하였습니다."; - apiResponse.setData(tagRankInfoList); - } else { - resultCode = "201"; - message = "인기 태그 목록이 존재하지 않습니다."; - } - - } catch (MainPageException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return mainPageService.selectPopularTagList(apiSecretKey); } @Operation(summary = "인기 지역 조회 API ", description = "메인 화면 - 지금 인기많은 핫 플레이스 부분에 해당하는 API", @@ -180,44 +54,6 @@ public ApiResponse selectPopularTagList(@RequestHeader("API-KEY") String apiSecr }) @GetMapping("/regions/popular") public ApiResponse selectPopularRegionList(@RequestHeader("API-KEY") String apiSecretKey) { - logger.info("CALL /api/v1/home/regions/popular"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(!apiSecretKey.equals(API_SECRET_KEY)) { - throw new MainPageException("100", "잘못된 접근입니다."); - } - - // 인기 지역 조회 - List regionRankInfoList = mainPageService.selectRegionRankList(); - - logger.info("@@ !regionRankInfoList.isEmpty()={}", !regionRankInfoList.isEmpty()); - if(!regionRankInfoList.isEmpty()) { - resultCode = "200"; - message = "인기 지역 조회 완료하였습니다."; - apiResponse.setData(regionRankInfoList); - } else { - resultCode = "201"; - message = "인기 지역 목록이 존재하지 않습니다."; - } - - } catch (MainPageException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return mainPageService.selectPopularRegionList(apiSecretKey); } } diff --git a/src/main/java/com/barogagi/mainPage/exception/MainPageException.java b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java index d42c740..11e04cf 100644 --- a/src/main/java/com/barogagi/mainPage/exception/MainPageException.java +++ b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java @@ -1,14 +1,12 @@ package com.barogagi.mainPage.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class MainPageException extends RuntimeException { - - private final String resultCode; +public class MainPageException extends BusinessException { public MainPageException(String resultCode, String message) { - super(message); - this.resultCode = resultCode; + super(resultCode, message); } } diff --git a/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java b/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java index 22b484f..972c4d4 100644 --- a/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java +++ b/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java @@ -21,4 +21,24 @@ public class MainPageResponse { private UserInfoResponseDTO userInfoResponseDTO; private List tagInfoList; private RegionInfoDTO regionInfoDTO; + + public static MainPageResponse resultData( + UserInfoResponseDTO userInfoResponseDTO, + List tagInfoList, + RegionInfoDTO regionInfoDTO, + String resultCode, + String message + ) + { + MainPageResponse mainPageResponse = new MainPageResponse(); + + mainPageResponse.userInfoResponseDTO = userInfoResponseDTO; + mainPageResponse.tagInfoList = tagInfoList; + mainPageResponse.regionInfoDTO = regionInfoDTO; + + mainPageResponse.resultCode = resultCode; + mainPageResponse.message = message; + + return mainPageResponse; + } } diff --git a/src/main/java/com/barogagi/mainPage/service/MainPageService.java b/src/main/java/com/barogagi/mainPage/service/MainPageService.java index ff6e9f4..3f766c4 100644 --- a/src/main/java/com/barogagi/mainPage/service/MainPageService.java +++ b/src/main/java/com/barogagi/mainPage/service/MainPageService.java @@ -1,13 +1,22 @@ package com.barogagi.mainPage.service; import com.barogagi.mainPage.dto.*; +import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.mainPage.mapper.MainPageMapper; +import com.barogagi.mainPage.response.MainPageResponse; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.response.ApiResponse; +import com.barogagi.util.MembershipUtil; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.Validator; +import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Map; @Service public class MainPageService { @@ -15,34 +24,140 @@ public class MainPageService { private static final Logger logger = LoggerFactory.getLogger(MainPageService.class); private final MainPageMapper mainPageMapper; + private final MembershipUtil membershipUtil; + private final Validator validator; @Autowired - public MainPageService(MainPageMapper mainPageMapper) { + public MainPageService( + MainPageMapper mainPageMapper, + MembershipUtil membershipUtil, + Validator validator + ) + { this.mainPageMapper = mainPageMapper; + this.membershipUtil = membershipUtil; + this.validator = validator; + } + + public MainPageResponse selectUserScheduleInfoProcess(HttpServletRequest request) { + + String resultCode = ""; + String message = ""; + List tagList = null; + RegionInfoDTO regionInfo = null; + + // 1. 회원번호 구하기 + Map membershipNoInfo = membershipUtil.membershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MainPageException( + String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message")) + ); + } + + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); + + // 2. 유저 일정 정보 조회 + UserInfoRequestDTO userInfoRequestDTO = new UserInfoRequestDTO(); + userInfoRequestDTO.setMembershipNo(membershipNo); + UserInfoResponseDTO userInfoResponseDTO = this.selectUserScheduleInfo(userInfoRequestDTO); + + if(null == userInfoResponseDTO) { + resultCode = ProcessResultCode.NOT_FOUND_SCHEDULE.getResultCode(); + message = ProcessResultCode.NOT_FOUND_SCHEDULE.getMessage(); + } else { + + resultCode = ProcessResultCode.FOUND_SCHEDULE.getResultCode(); + message = ProcessResultCode.FOUND_SCHEDULE.getMessage(); + + // 3. 해당 schedule에 대한 태그 목록 조회 + userInfoRequestDTO.setScheduleNum(userInfoResponseDTO.getScheduleNum()); + tagList = this.selectScheduleTag(userInfoRequestDTO); + + // 4. 해당 plan에 대한 region 정보 조회 + userInfoRequestDTO.setPlanNum(userInfoResponseDTO.getPlanNum()); + regionInfo = this.selectScheduleRegionInfo(userInfoRequestDTO); + } + + return MainPageResponse.resultData(userInfoResponseDTO, tagList, regionInfo, resultCode, message); + } + + public ApiResponse selectPopularTagList(String apiSecretKey) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new MainPageException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 인기 태그 조회 + List tagRankInfoList = this.selectTagRankList(); + if(tagRankInfoList.isEmpty()) { + throw new MainPageException( + ProcessResultCode.NOT_FOUND_POPULAR_TAG.getResultCode(), + ProcessResultCode.NOT_FOUND_POPULAR_TAG.getMessage() + ); + + } + + return ApiResponse.resultData( + tagRankInfoList, + ProcessResultCode.FOUND_POPULAR_TAG.getResultCode(), + ProcessResultCode.FOUND_POPULAR_TAG.getMessage() + ); + } + + public ApiResponse selectPopularRegionList(String apiSecretKey) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new MainPageException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 인기 지역 조회 + List regionRankInfoList = this.selectRegionRankList(); + + if(regionRankInfoList.isEmpty()) { + throw new MainPageException( + ProcessResultCode.NOT_FOUND_POPULAR_REGION.getResultCode(), + ProcessResultCode.NOT_FOUND_POPULAR_REGION.getMessage() + ); + } + + return ApiResponse.resultData( + regionRankInfoList, + ProcessResultCode.FOUND_POPULAR_REGION.getResultCode(), + ProcessResultCode.FOUND_POPULAR_REGION.getMessage() + ); } // 유저 일정 조회 - public UserInfoResponseDTO selectUserScheduleInfo(UserInfoRequestDTO userInfoRequestDTO) throws Exception { + public UserInfoResponseDTO selectUserScheduleInfo(UserInfoRequestDTO userInfoRequestDTO) { return mainPageMapper.selectUserScheduleInfo(userInfoRequestDTO); } // 해당 schedule에 대한 태그 목록 조회 - public List selectScheduleTag(UserInfoRequestDTO userInfoRequestDTO) throws Exception { + public List selectScheduleTag(UserInfoRequestDTO userInfoRequestDTO) { return mainPageMapper.selectScheduleTag(userInfoRequestDTO); } // 해당 plan에 대한 region 정보 조회 - public RegionInfoDTO selectScheduleRegionInfo(UserInfoRequestDTO userInfoRequestDTO) throws Exception { + public RegionInfoDTO selectScheduleRegionInfo(UserInfoRequestDTO userInfoRequestDTO) { return mainPageMapper.selectScheduleRegionInfo(userInfoRequestDTO); } // 인기 지역 조회 - public List selectRegionRankList() throws Exception { + public List selectRegionRankList() { return mainPageMapper.selectRegionRankList(); } // 인기 태그 조회 - public List selectTagRankList() throws Exception { + public List selectTagRankList() { return mainPageMapper.selectTagRankList(); } } diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index 3338461..7641ee8 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -1,357 +1,78 @@ package com.barogagi.member.basic.join.controller; -import com.barogagi.config.PasswordConfig; -import com.barogagi.member.basic.join.dto.NickNameDTO; -import com.barogagi.member.basic.join.service.JoinService; -import com.barogagi.member.basic.join.dto.JoinDTO; +import com.barogagi.member.basic.join.service.BasicJoinService; import com.barogagi.member.basic.join.dto.JoinRequestDTO; -import com.barogagi.member.login.exception.InvalidRefreshTokenException; -import com.barogagi.member.login.service.AccountService; -import com.barogagi.member.login.service.AuthService; +import com.barogagi.member.login.dto.RefreshTokenRequestDTO; import com.barogagi.response.ApiResponse; -import com.barogagi.util.EncryptUtil; -import com.barogagi.util.InputValidate; -import com.barogagi.util.Validator; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; -import java.util.Map; -import java.util.Optional; - @Tag(name = "일반 회원가입", description = "일반 회원가입 관련 API") @RestController @RequestMapping("/api/v1/users") public class JoinController { - private static final Logger logger = LoggerFactory.getLogger(JoinController.class); - - private final JoinService joinService; - private final AccountService accountService; - private final AuthService authService; - private final InputValidate inputValidate; - private final EncryptUtil encryptUtil; - private final Validator validator; - private final PasswordConfig passwordConfig; - private final String API_SECRET_KEY; + private final BasicJoinService basicJoinService; @Autowired - public JoinController(Environment environment, - JoinService joinService, - AccountService accountService, - AuthService authService, - InputValidate inputValidate, - EncryptUtil encryptUtil, - Validator validator, - PasswordConfig passwordConfig) { - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); - this.joinService = joinService; - this.accountService = accountService; - this.authService = authService; - this.inputValidate = inputValidate; - this.encryptUtil = encryptUtil; - this.validator = validator; - this.passwordConfig = passwordConfig; + public JoinController(BasicJoinService basicJoinService) { + this.basicJoinService = basicJoinService; } @Operation(summary = "아이디 중복 체크 기능", description = "아이디 중복 체크 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 아이디 사용이 가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "아이디를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디가 아닙니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "해당 아이디 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/userid/exists") public ApiResponse checkUserId(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String userId) { - - logger.info("CALL /api/v1/users/userId/exists"); - logger.info("[input] API_SECRET_KEY={}", apiSecretKey); - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(apiSecretKey.equals(API_SECRET_KEY)){ - - if(inputValidate.isEmpty(userId)) { - resultCode = "101"; - message = "아이디를 입력해주세요."; - } else{ - - if(!validator.isValidId(userId)) { - resultCode = "102"; - message = "적합한 아이디가 아닙니다."; - } else { - JoinDTO joinDTO = new JoinDTO(); - joinDTO.setUserId(userId); - - int checkUserId = joinService.checkUserId(joinDTO); - logger.info("@@ checkUserId={}", checkUserId); - - if(checkUserId > 0){ - resultCode = "300"; - message = "해당 아이디 사용이 불가능합니다."; - - } else{ - resultCode = "200"; - message = "해당 아이디 사용이 가능합니다."; - } - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return basicJoinService.checkUserId(apiSecretKey, userId); } @Operation(summary = "회원가입 정보 저장 기능", description = "회원가입 정보 저장 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원가입에 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "회원가입에 필요한 정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 아이디에 대한 회원 정보가 이미 존재합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원가입에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping - public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestDTO){ - - logger.info("CALL /api/v1/users"); - logger.info("[input] API_SECRET_KEY={}", joinRequestDTO.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(joinRequestDTO.getApiSecretKey().equals(API_SECRET_KEY)){ - - // 필수 입력값(아이디, 비밀번호, 휴대전화번호 값이 빈 값이 아닌지 확인) - // 선택 입력값(이메일, 생년월일, 성별, 닉네임) - if(inputValidate.isEmpty(joinRequestDTO.getUserId()) || inputValidate.isEmpty(joinRequestDTO.getPassword()) || inputValidate.isEmpty(joinRequestDTO.getTel())){ - - // 필수 입력값 중 빈 값이 존재. insert 중지 - resultCode = "101"; - message = "회원가입에 필요한 정보를 입력해주세요."; - - } else{ - - // 아이디, 비밀번호, 닉네임 적합성 검사 - if(!(validator.isValidId(joinRequestDTO.getUserId()) && validator.isValidPassword(joinRequestDTO.getPassword()) && validator.isValidNickname(joinRequestDTO.getNickName()))){ - resultCode = "102"; - message = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."; - } else { - // 입력값 암호화 & 값 세팅 - // 휴대전화번호, 비밀번호 암호화 - joinRequestDTO.setTel(encryptUtil.encrypt(joinRequestDTO.getTel().replaceAll("[^0-9]", ""))); - - // 이메일 값이 넘어오면 암호화 - if(!inputValidate.isEmpty(joinRequestDTO.getEmail())){ - joinRequestDTO.setEmail(encryptUtil.encrypt(joinRequestDTO.getEmail())); - } - - String encodedPassword = passwordConfig.passwordEncoder().encode(joinRequestDTO.getPassword()); - joinRequestDTO.setPassword(encodedPassword); - - // 회원 정보 저장(회원가입) - JoinDTO joinDTO = new JoinDTO(); - joinDTO.setUserId(joinRequestDTO.getUserId()); - joinDTO.setPassword(joinRequestDTO.getPassword()); - joinDTO.setEmail(joinRequestDTO.getEmail()); - joinDTO.setBirth(joinRequestDTO.getBirth().replaceAll("[^0-9]", "")); - joinDTO.setTel(joinRequestDTO.getTel()); - joinDTO.setGender(joinRequestDTO.getGender()); - joinDTO.setNickName(joinRequestDTO.getNickName()); - joinDTO.setJoinType("BASIC"); - - // 아이디 중복 검증 - int duplicateUserId = joinService.checkUserId(joinDTO); - - if(duplicateUserId > 0) { - resultCode = "103"; - message = "해당 아이디에 대한 회원 정보가 이미 존재합니다."; - } else { - int insertResult = joinService.insertMembershipInfo(joinDTO); - logger.info("@@ insertResult={}", insertResult); - - if(insertResult > 0){ - resultCode = "200"; - message = "회원가입에 성공하였습니다."; - } else{ - resultCode = "300"; - message = "회원가입에 실패하였습니다."; - } - } - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + public ApiResponse signUp(@RequestBody JoinRequestDTO joinRequestDTO) { + return basicJoinService.signUp(joinRequestDTO); } - @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API입니다.", + @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "이용 가능한 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용 가능한 닉네임입니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "닉네임 데이터를 보내주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합하지 않는 닉네임입니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "이미 존재하는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 닉네임 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/nickname/exists") - public ApiResponse checkDuplicateNickname(@RequestHeader("API-KEY") String apiSecretKey, - @RequestParam String nickname){ - - logger.info("CALL /api/v1/user/nickname/exists"); - logger.info("[input] API_SECRET_KEY={}", apiSecretKey); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(apiSecretKey.equals(API_SECRET_KEY)){ - - // 필수 입력값 - if(inputValidate.isEmpty(nickname)){ - - // 필수 입력값 중 빈 값이 존재. insert 중지 - resultCode = "101"; - message = "닉네임 정보를 입력해주세요."; - - } else{ - - if(!validator.isValidNickname(nickname)) { - resultCode = "102"; - message = "적합하지 않는 닉네임입니다."; - } else { - - NickNameDTO nickNameDTO = new NickNameDTO(); - nickNameDTO.setNickName(nickname); - - int nickNameCnt = joinService.checkNickName(nickNameDTO); - logger.info("nickNameCnt={}", nickNameCnt); - if(nickNameCnt > 0) { - resultCode = "103"; - message = "이미 존재하는 닉네임입니다."; - } else { - resultCode = "200"; - message = "이용 가능한 닉네임입니다."; - } - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + public ApiResponse checkDuplicateNickname(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String nickname){ + return basicJoinService.checkNickname(apiSecretKey, nickname); } @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "refresh token이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @DeleteMapping("/me") - public ApiResponse deleteMe(@RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body) { - - logger.info("CALL /api/v1/users/me"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - String refreshToken = Optional.ofNullable(refreshHeader) - .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) - .orElse(null); - - if (refreshToken == null || refreshToken.isBlank()) { - throw new InvalidRefreshTokenException("100", "refresh token이 존재하지 않습니다."); - } - - // refresh token을 이용해서 membershipNo 구하기 - - Map resultMap = authService.selectUserInfoByToken(refreshToken); - if(!resultMap.get("resultCode").equals("200")) { - throw new InvalidRefreshTokenException(resultMap.get("resultCode"), resultMap.get("message")); - } - - String membershipNo = resultMap.get("membershipNo"); - - int deleteResult = accountService.deleteMyAccount(membershipNo); - if(deleteResult > 0) { - resultCode = "200"; - message = "회원 탈퇴되었습니다."; - } else { - resultCode = "300"; - message = "회원 탈퇴 실패하였습니다."; - } - - } catch (InvalidRefreshTokenException ex) { - resultCode = ex.getCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + public ApiResponse deleteAccount(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { + return basicJoinService.deleteAccount(refreshTokenRequestDTO); } } diff --git a/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java b/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java new file mode 100644 index 0000000..6c1b42f --- /dev/null +++ b/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java @@ -0,0 +1,12 @@ +package com.barogagi.member.basic.join.exception; + +import com.barogagi.config.exception.BusinessException; +import lombok.Getter; + +@Getter +public class JoinException extends BusinessException { + + public JoinException(String resultCode, String message) { + super(resultCode, message); + } +} diff --git a/src/main/java/com/barogagi/member/basic/join/mapper/JoinMapper.java b/src/main/java/com/barogagi/member/basic/join/mapper/JoinMapper.java index 4b17054..7929081 100644 --- a/src/main/java/com/barogagi/member/basic/join/mapper/JoinMapper.java +++ b/src/main/java/com/barogagi/member/basic/join/mapper/JoinMapper.java @@ -11,10 +11,10 @@ public interface JoinMapper { int insertMemberInfo(JoinDTO vo); // 아이디 중복 체크 - int checkUserId(JoinDTO vo); + int selectUserIdCnt(JoinDTO vo); // 닉네임 중복 체크 - int checkNickName(NickNameDTO dto); + int selectNicknameCnt(NickNameDTO dto); // 회원번호 중복 체크 int checkDuplicateMembershipNo(String membershipNo); diff --git a/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java b/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java new file mode 100644 index 0000000..2ef28de --- /dev/null +++ b/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java @@ -0,0 +1,285 @@ +package com.barogagi.member.basic.join.service; + +import com.barogagi.config.PasswordConfig; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.member.basic.join.dto.JoinDTO; +import com.barogagi.member.basic.join.dto.JoinRequestDTO; +import com.barogagi.member.basic.join.dto.NickNameDTO; +import com.barogagi.member.basic.join.exception.JoinException; +import com.barogagi.member.login.dto.RefreshTokenRequestDTO; +import com.barogagi.member.login.exception.InvalidRefreshTokenException; +import com.barogagi.member.login.service.AccountService; +import com.barogagi.member.login.service.AuthService; +import com.barogagi.response.ApiResponse; +import com.barogagi.util.EncryptUtil; +import com.barogagi.util.InputValidate; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.Validator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class BasicJoinService { + + private final Validator validator; + private final InputValidate inputValidate; + private final JoinService joinService; + private final AuthService authService; + private final AccountService accountService; + private final EncryptUtil encryptUtil; + private final PasswordConfig passwordConfig; + + @Autowired + public BasicJoinService( Validator validator + , InputValidate inputValidate + , JoinService joinService + , AuthService authService + , AccountService accountService + , EncryptUtil encryptUtil + , PasswordConfig passwordConfig) { + + this.validator = validator; + this.inputValidate = inputValidate; + this.joinService = joinService; + this.authService = authService; + this.accountService = accountService; + this.encryptUtil = encryptUtil; + this.passwordConfig = passwordConfig; + } + + // 닉네임 중복 체크 service + public ApiResponse checkNickname(String apiSecretKey, String nickname) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new JoinException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(nickname)) { + throw new JoinException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 적합한 닉네임인지 확인 + if(!validator.isValidNickname(nickname)) { + throw new JoinException( + ProcessResultCode.INVALID_NICKNAME.getResultCode(), + ProcessResultCode.INVALID_NICKNAME.getMessage() + ); + } + + // 4. 닉네임 중복 체크 + NickNameDTO nickNameDTO = new NickNameDTO(); + nickNameDTO.setNickName(nickname); + + int nickNameCnt = joinService.selectNicknameCnt(nickNameDTO); + if(nickNameCnt > 0) { + throw new JoinException( + ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), + ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.AVAILABLE_NICKNAME.getResultCode(), + ProcessResultCode.AVAILABLE_NICKNAME.getMessage() + ); + } + + // 아이디 중복 체크 service + public ApiResponse checkUserId(String apiSecretKey, String userId) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new JoinException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(userId)) { + throw new JoinException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 적합한 아이디인지 확인 + if(!validator.isValidId(userId)) { + throw new JoinException( + ProcessResultCode.INVALID_USER_ID.getResultCode(), + ProcessResultCode.INVALID_USER_ID.getMessage() + ); + } + + // 4. 아이디 중복 체크 + JoinDTO joinDTO = new JoinDTO(); + joinDTO.setUserId(userId); + + int checkUserId = joinService.selectUserIdCnt(joinDTO); + + if(checkUserId > 0) { + throw new JoinException( + ProcessResultCode.UNAVAILABLE_USER_ID.getResultCode(), + ProcessResultCode.UNAVAILABLE_USER_ID.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.AVAILABLE_USER_ID.getResultCode(), + ProcessResultCode.AVAILABLE_USER_ID.getMessage() + ); + } + + public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(joinRequestDTO.getApiSecretKey())) { + throw new JoinException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + // 필수 입력값(아이디, 비밀번호, 휴대전화번호 값이 빈 값이 아닌지 확인) + // 선택 입력값(이메일, 생년월일, 성별, 닉네임) + if(inputValidate.isEmpty(joinRequestDTO.getUserId()) + || inputValidate.isEmpty(joinRequestDTO.getPassword()) + || inputValidate.isEmpty(joinRequestDTO.getTel())) + { + throw new JoinException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 적합한 아이디인지 확인 + // 아이디, 비밀번호 적합성 검사 + if(!( + validator.isValidId(joinRequestDTO.getUserId()) + && validator.isValidPassword(joinRequestDTO.getPassword())) + ) { + throw new JoinException( + ProcessResultCode.INVALID_SIGN_UP.getResultCode(), + ProcessResultCode.INVALID_SIGN_UP.getMessage() + ); + } + + // 4. 암호화 + // 휴대전화번호, 비밀번호 암호화 + joinRequestDTO.setTel(encryptUtil.encrypt(joinRequestDTO.getTel().replaceAll("[^0-9]", ""))); + String encodedPassword = passwordConfig.passwordEncoder().encode(joinRequestDTO.getPassword()); + joinRequestDTO.setPassword(encodedPassword); + + // 이메일 값이 넘어오면 암호화 + if(!inputValidate.isEmpty(joinRequestDTO.getEmail())){ + joinRequestDTO.setEmail(encryptUtil.encrypt(joinRequestDTO.getEmail())); + } + + JoinDTO joinDTO = new JoinDTO(); + joinDTO.setUserId(joinRequestDTO.getUserId()); + joinDTO.setPassword(joinRequestDTO.getPassword()); + joinDTO.setEmail(joinRequestDTO.getEmail()); + + if(null != joinRequestDTO.getBirth()) { + joinRequestDTO.setBirth(joinRequestDTO.getBirth().replaceAll("[^0-9]", "")); + } + joinDTO.setBirth(joinRequestDTO.getBirth()); + joinDTO.setTel(joinRequestDTO.getTel()); + joinDTO.setGender(joinRequestDTO.getGender()); + joinDTO.setNickName(joinRequestDTO.getNickName()); + joinDTO.setJoinType("BASIC"); + + // 아이디 중복 검증 + int duplicateUserId = joinService.selectUserIdCnt(joinDTO); + + // 5. 아이디 중복 검증 + if(duplicateUserId > 0) { + throw new JoinException( + ProcessResultCode.UNAVAILABLE_USER_ID.getResultCode(), + ProcessResultCode.UNAVAILABLE_USER_ID.getMessage() + ); + } + + // 닉네임 값이 넘어올 경우 중복 검사 + if(!inputValidate.isEmpty(joinDTO.getNickName())) { + + // 닉네임 적합성 검사 + if(!validator.isValidNickname(joinRequestDTO.getNickName())) { + throw new JoinException( + ProcessResultCode.INVALID_NICKNAME.getResultCode(), + ProcessResultCode.INVALID_NICKNAME.getMessage() + ); + } + + NickNameDTO nickNameDTO = new NickNameDTO(); + nickNameDTO.setNickName(joinDTO.getNickName()); + int selectNicknameCnt = joinService.selectNicknameCnt(nickNameDTO); + + if(selectNicknameCnt > 0) { + throw new JoinException( + ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), + ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() + ); + } + } + + // 6. 회원 정보 저장 + int insertResult = joinService.insertMembershipInfo(joinDTO); + if(insertResult <= 0){ + throw new JoinException( + ProcessResultCode.FAIL_SIGN_UP.getResultCode(), + ProcessResultCode.FAIL_SIGN_UP.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_SIGN_UP.getResultCode(), + ProcessResultCode.SUCCESS_SIGN_UP.getMessage() + ); + } + + public ApiResponse deleteAccount(RefreshTokenRequestDTO refreshTokenRequestDTO) { + + // 1. refresh token이 공백 또는 null인지 확인 + if(inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { + throw new JoinException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage()); + } + + // 2. refresh token을 이용해서 membershipNo 구하기 + Map resultMap = authService.selectUserInfoByToken(refreshTokenRequestDTO.getRefreshToken()); + if(!resultMap.get("resultCode").equals("200")) { + throw new InvalidRefreshTokenException( + resultMap.get("resultCode"), + resultMap.get("message") + ); + } + + int deleteResult = accountService.deleteMyAccount(resultMap.get("membershipNo")); + if(deleteResult <= 0) { + throw new JoinException( + ProcessResultCode.FAIL_DELETE_ACCOUNT.getResultCode(), + ProcessResultCode.FAIL_DELETE_ACCOUNT.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_DELETE_ACCOUNT.getResultCode(), + ProcessResultCode.SUCCESS_DELETE_ACCOUNT.getMessage() + ); + } +} + + diff --git a/src/main/java/com/barogagi/member/basic/join/service/JoinService.java b/src/main/java/com/barogagi/member/basic/join/service/JoinService.java index 63bdc65..c33f06a 100644 --- a/src/main/java/com/barogagi/member/basic/join/service/JoinService.java +++ b/src/main/java/com/barogagi/member/basic/join/service/JoinService.java @@ -9,9 +9,7 @@ import org.springframework.stereotype.Service; import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; @Service public class JoinService { @@ -21,55 +19,16 @@ public class JoinService { private static final SecureRandom random = new SecureRandom(); private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - @Autowired - private JoinMapper joinMapper; - - // 회원가입 정보 저장 service - public int insertMembershipInfo(JoinDTO vo) throws Exception { - - int result = 0; - - String membershipNo = ""; - - while(true) { - // 랜덤 회원번호 생성 - membershipNo = this.createRandomStr(); - - logger.info("membershipNo.isEmpty()={}", membershipNo.isEmpty()); - if(membershipNo.isEmpty()) { - break; - } - - // 회원번호 중복 체크 - boolean checkDuplicateMembershipNo = this.checkDuplicateMemberNo(membershipNo); - logger.info("checkDuplicateMembershipNo={}", checkDuplicateMembershipNo); - if(!checkDuplicateMembershipNo) { - break; - } - } + private final JoinMapper joinMapper; - logger.info("membershipNo.isEmpty()={}", membershipNo.isEmpty()); - if(!membershipNo.isEmpty()) { // 회원번호가 비어있을 경우 저장 X - vo.setMembershipNo(membershipNo); - result = this.insertMemberInfo(vo); - } - - return result; - } - - // 회원가입 정보 저장 기능 - public int insertMemberInfo(JoinDTO vo) throws Exception{ - return joinMapper.insertMemberInfo(vo); - } - - // 아이디 중복 체크 - public int checkUserId(JoinDTO vo) throws Exception{ - return joinMapper.checkUserId(vo); + @Autowired + public JoinService(JoinMapper joinMapper) { + this.joinMapper = joinMapper; } - // 닉네임 중복 체크 - public int checkNickName(NickNameDTO nickNameDTO) throws Exception{ - return joinMapper.checkNickName(nickNameDTO); + // 닉네임 개수 구하기 + public int selectNicknameCnt(NickNameDTO nickNameDTO) { + return joinMapper.selectNicknameCnt(nickNameDTO); } // 회원번호 랜덤값 생성 @@ -110,7 +69,7 @@ public String createRandomStr() { } // 회원번호 중복 체크 - public boolean checkDuplicateMemberNo(String membershipNo) throws Exception { + public boolean checkDuplicateMemberNo(String membershipNo) { boolean duplicateFlag = false; int membershipNoCnt = joinMapper.checkDuplicateMembershipNo(membershipNo); @@ -122,4 +81,47 @@ public boolean checkDuplicateMemberNo(String membershipNo) throws Exception { return duplicateFlag; } + + // 회원가입 정보 저장 service + public int insertMembershipInfo(JoinDTO vo) { + + int result = 0; + + String membershipNo = ""; + + while(true) { + // 랜덤 회원번호 생성 + membershipNo = this.createRandomStr(); + + logger.info("membershipNo.isEmpty()={}", membershipNo.isEmpty()); + if(membershipNo.isEmpty()) { + break; + } + + // 회원번호 중복 체크 + boolean checkDuplicateMembershipNo = this.checkDuplicateMemberNo(membershipNo); + logger.info("checkDuplicateMembershipNo={}", checkDuplicateMembershipNo); + if(!checkDuplicateMembershipNo) { + break; + } + } + + logger.info("membershipNo.isEmpty()={}", membershipNo.isEmpty()); + if(!membershipNo.isEmpty()) { // 회원번호가 비어있을 경우 저장 X + vo.setMembershipNo(membershipNo); + result = this.insertMemberInfo(vo); + } + + return result; + } + + // 회원가입 정보 저장 기능 + public int insertMemberInfo(JoinDTO vo) { + return joinMapper.insertMemberInfo(vo); + } + + // 아이디 개수 구하기 + public int selectUserIdCnt(JoinDTO vo) { + return joinMapper.selectUserIdCnt(vo); + } } diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index ec586dd..b5e7342 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -1,193 +1,47 @@ package com.barogagi.member.info.controller; -import com.barogagi.member.basic.join.dto.NickNameDTO; -import com.barogagi.member.basic.join.service.JoinService; -import com.barogagi.member.info.exception.MemberInfoException; -import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.dto.MemberRequestDTO; import com.barogagi.member.info.service.MemberService; import com.barogagi.response.ApiResponse; -import com.barogagi.util.EncryptUtil; -import com.barogagi.util.MembershipUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; -import java.util.Map; - @Tag(name = "회원 정보", description = "회원 정보 관련 API") @RestController @RequestMapping("/api/v1/members") public class InfoController { - private static final Logger logger = LoggerFactory.getLogger(InfoController.class); - private final MemberService memberService; - private final JoinService joinService; - - private final EncryptUtil encryptUtil; - private final MembershipUtil membershipUtil; - - public InfoController(MemberService memberService, - JoinService joinService, - EncryptUtil encryptUtil, - MembershipUtil membershipUtil) { + public InfoController(MemberService memberService) { this.memberService = memberService; - this.joinService = joinService; - this.encryptUtil = encryptUtil; - this.membershipUtil = membershipUtil; } @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 정보 조회가 완료되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping public ApiResponse selectMemberInfo(HttpServletRequest request) { - logger.info("CALL /api/v1/members"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - // 회원번호 구하기 - Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), - String.valueOf(membershipNoInfo.get("message"))); - } - - String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); - - // 회원 정보 조회 - Member memberInfo = memberService.findByMembershipNo(membershipNo); - if(null == memberInfo) { - throw new MemberInfoException("402", "해당 사용자에 대한 정보가 존재하지 않습니다."); - } - - // 이메일 복호화 - memberInfo.setEmail(encryptUtil.decrypt(memberInfo.getEmail())); - - // 전화번호 복호화 - memberInfo.setTel(encryptUtil.decrypt(memberInfo.getTel())); - - // 비밀번호는 보내주지 않는다 - memberInfo.setPassword(""); - - resultCode = "200"; - message = "회원 정보 조회가 완료되었습니다."; - apiResponse.setData(memberInfo); - - } catch (MemberInfoException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return memberService.selectMemberInfo(request); } @Operation(summary = "회원 정보 수정", description = "회원 정보 조회 수정입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "이미 해당 닉네임이 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 닉네임 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자 정보 수정 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용자 정보 수정 완료하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PatchMapping - public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody MemberRequestDTO memberRequestDto) { - logger.info("CALL /api/v1/members"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - logger.info("param gender={}", memberRequestDto.getGender()); - logger.info("param nickName={}", memberRequestDto.getNickName()); - - // 회원번호 구하기 - Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), - String.valueOf(membershipNoInfo.get("message"))); - } - - String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); - logger.info("@@ membershipNo={}", membershipNo); - - // 회원 정보 조회 - Member memberInfo = memberService.findByMembershipNo(membershipNo); - if(null == memberInfo) { - throw new MemberInfoException("402", "해당 사용자에 대한 정보가 존재하지 않습니다."); - } - - // 생년월일 - if(!memberRequestDto.getBirth().isEmpty()) { - memberInfo.setBirth(memberRequestDto.getBirth().replaceAll("[^0-9]", "")); - } - - // 성별 (M : 남 / W : 여) - if(!memberRequestDto.getGender().isEmpty()) { - memberInfo.setGender(memberRequestDto.getGender()); - } - - // 닉네임(중복X) - if(!memberRequestDto.getNickName().isEmpty()) { - NickNameDTO nickNameRequest = new NickNameDTO(); - nickNameRequest.setNickName(memberRequestDto.getNickName()); - int nickNameCnt = joinService.checkNickName(nickNameRequest); - - logger.info("@@ nickNameCnt={}", nickNameCnt); - if(nickNameCnt > 0) { - throw new MemberInfoException("403", "이미 해당 닉네임이 존재합니다."); - } - - memberInfo.setNickName(memberRequestDto.getNickName()); - } - - int updateMemberInfo = memberService.updateMemberInfo(memberInfo); - logger.info("@@ updateMemberInfo={}", updateMemberInfo); - if(updateMemberInfo <= 0) { - throw new MemberInfoException("404", "사용자 정보 수정 실패하였습니다."); - } - - // 사용자 정보 수정 성공 - resultCode = "200"; - message = "사용자 정보 수정 완료하였습니다."; - - } catch (MemberInfoException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody MemberRequestDTO memberRequestDTO) { + return memberService.updateMemberProcess(request, memberRequestDTO); } } diff --git a/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java index 3e7c3f7..8888ead 100644 --- a/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java +++ b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java @@ -1,13 +1,12 @@ package com.barogagi.member.info.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class MemberInfoException extends RuntimeException{ - private final String resultCode; +public class MemberInfoException extends BusinessException { public MemberInfoException(String resultCode, String message) { - super(message); - this.resultCode = resultCode; + super(resultCode, message); } } diff --git a/src/main/java/com/barogagi/member/info/service/MemberService.java b/src/main/java/com/barogagi/member/info/service/MemberService.java index c791062..1efbe21 100644 --- a/src/main/java/com/barogagi/member/info/service/MemberService.java +++ b/src/main/java/com/barogagi/member/info/service/MemberService.java @@ -1,25 +1,154 @@ package com.barogagi.member.info.service; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.member.basic.join.dto.NickNameDTO; +import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.info.dto.Member; +import com.barogagi.member.info.dto.MemberRequestDTO; +import com.barogagi.member.info.exception.MemberInfoException; import com.barogagi.member.info.mapper.MemberMapper; +import com.barogagi.response.ApiResponse; +import com.barogagi.util.EncryptUtil; +import com.barogagi.util.InputValidate; +import com.barogagi.util.MembershipUtil; +import com.barogagi.config.resultCode.ResultCode; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Map; + @Service public class MemberService { + private final MemberMapper memberMapper; + private final MembershipUtil membershipUtil; + private final EncryptUtil encryptUtil; + private final JoinService joinService; + private final InputValidate inputValidate; + @Autowired - private MemberMapper memberMapper; + public MemberService(MemberMapper memberMapper, + MembershipUtil membershipUtil, + EncryptUtil encryptUtil, + JoinService joinService, + InputValidate inputValidate) + { + this.memberMapper = memberMapper; + this.membershipUtil = membershipUtil; + this.encryptUtil = encryptUtil; + this.joinService = joinService; + this.inputValidate = inputValidate; + } + + public ApiResponse selectMemberInfo(HttpServletRequest request) { + + // 1. 회원번호 구하기 + Map membershipNoInfo = membershipUtil.membershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MemberInfoException( + String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message")) + ); + } + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); + + // 2. 회원 정보 조회 + Member memberInfo = this.findByMembershipNo(membershipNo); + if(null == memberInfo) { + throw new MemberInfoException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + ); + } + + // 이메일 복호화 + memberInfo.setEmail(encryptUtil.decrypt(memberInfo.getEmail())); + + // 전화번호 복호화 + memberInfo.setTel(encryptUtil.decrypt(memberInfo.getTel())); + + // 비밀번호는 보내주지 않는다 + memberInfo.setPassword(""); + + return ApiResponse.resultData( + memberInfo, + ProcessResultCode.FOUND_USER_INFO.getResultCode(), + ProcessResultCode.FOUND_USER_INFO.getMessage() + ); + } + + public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequestDTO memberRequestDTO) { + + // 1. 회원번호 구하기 + Map membershipNoInfo = membershipUtil.membershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MemberInfoException( + String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message"))); + } + + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); + + // 2. 회원 정보 조회 + Member memberInfo = this.findByMembershipNo(membershipNo); + if(null == memberInfo) { + throw new MemberInfoException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + ); + } + + // 3. 데이터 처리 + // 생년월일 + if(!inputValidate.isEmpty(memberRequestDTO.getBirth())) { + memberInfo.setBirth(memberRequestDTO.getBirth().replaceAll("[^0-9]", "")); + } + + // 성별 (M : 남 / W : 여) + if(!inputValidate.isEmpty(memberRequestDTO.getGender())) { + memberInfo.setGender(memberRequestDTO.getGender()); + } + + // 닉네임(중복X) + if(!inputValidate.isEmpty(memberRequestDTO.getNickName())) { + NickNameDTO nickNameRequest = new NickNameDTO(); + nickNameRequest.setNickName(memberRequestDTO.getNickName()); + int nickNameCnt = joinService.selectNicknameCnt(nickNameRequest); + + if(nickNameCnt > 0) { + throw new MemberInfoException( + ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), + ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() + ); + } + + memberInfo.setNickName(memberRequestDTO.getNickName()); + } + + int updateMemberInfo = this.updateMemberInfo(memberInfo); + if(updateMemberInfo <= 0) { + throw new MemberInfoException( + ProcessResultCode.FAIL_UPDATE_USER_INFO.getResultCode(), + ProcessResultCode.FAIL_UPDATE_USER_INFO.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_UPDATE_USER_INFO.getResultCode(), + ProcessResultCode.SUCCESS_UPDATE_USER_INFO.getMessage() + ); + } - public Member findByMembershipNo(String membershipNo) throws Exception { + public Member findByMembershipNo(String membershipNo) { return memberMapper.findByMembershipNo(membershipNo); } - public Member selectUserMembershipInfo(String userId) throws Exception { + public Member selectUserMembershipInfo(String userId) { return memberMapper.selectUserMembershipInfo(userId); } - public int updateMemberInfo(Member member) throws Exception { + public int updateMemberInfo(Member member) { return memberMapper.updateMemberInfo(member); } } diff --git a/src/main/java/com/barogagi/member/login/controller/AuthController.java b/src/main/java/com/barogagi/member/login/controller/AuthController.java index 3d024e2..e69de29 100644 --- a/src/main/java/com/barogagi/member/login/controller/AuthController.java +++ b/src/main/java/com/barogagi/member/login/controller/AuthController.java @@ -1,36 +0,0 @@ -package com.barogagi.member.login.controller; - -import com.barogagi.member.login.dto.*; -import com.barogagi.member.login.exception.InvalidRefreshTokenException; -import com.barogagi.member.login.service.AccountService; -import com.barogagi.member.login.service.AuthService; -import com.barogagi.response.ApiResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; -import java.util.Optional; - -@Tag(name = "TOKEN 재발급, 로그아웃, 탈퇴", description = "TOKEN 재발급, 로그아웃, 탈퇴 관련 API") -@RestController -@RequestMapping("/auth") -public class AuthController { - - private static final Logger logger = LoggerFactory.getLogger(AuthController.class); - - private final AuthService authService; - private final AccountService accountService; - - public AuthController(AuthService authService, - AccountService accountService) { - this.authService = authService; - this.accountService = accountService; - } -} - - diff --git a/src/main/java/com/barogagi/member/login/controller/LoginController.java b/src/main/java/com/barogagi/member/login/controller/LoginController.java index 3e56b77..c33fa6e 100644 --- a/src/main/java/com/barogagi/member/login/controller/LoginController.java +++ b/src/main/java/com/barogagi/member/login/controller/LoginController.java @@ -1,73 +1,29 @@ package com.barogagi.member.login.controller; -import com.barogagi.config.PasswordConfig; -import com.barogagi.member.info.dto.Member; -import com.barogagi.member.info.service.MemberService; import com.barogagi.member.login.dto.*; -import com.barogagi.member.login.exception.InvalidRefreshTokenException; -import com.barogagi.member.login.exception.LoginException; -import com.barogagi.member.login.service.AuthService; import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; -import com.barogagi.util.EncryptUtil; -import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; -import org.springframework.http.ResponseEntity; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; -import java.util.*; - @Tag(name = "일반 로그인 & 토큰 재발급", description = "일반 로그인, 토큰 재발급 관련 API") @RestController @RequestMapping("/api/v1/auth") public class LoginController { - private static final Logger logger = LoggerFactory.getLogger(LoginController.class); - - private final InputValidate inputValidate; - - private final EncryptUtil encryptUtil; private final LoginService loginService; - private final MemberService memberService; - private final AuthService authService; - - private final PasswordEncoder passwordEncoder; - - private final PasswordConfig passwordConfig; - - private final String API_SECRET_KEY; @Autowired - public LoginController(Environment environment, - InputValidate inputValidate, - EncryptUtil encryptUtil, - LoginService loginService, - MemberService memberService, - AuthService authService, - PasswordEncoder passwordEncoder, - PasswordConfig passwordConfig){ - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); - - this.inputValidate = inputValidate; - this.encryptUtil = encryptUtil; + public LoginController(LoginService loginService){ this.loginService = loginService; - this.memberService = memberService; - this.authService = authService; - this.passwordEncoder = passwordEncoder; - this.passwordConfig = passwordConfig; } @Operation(summary = "로그인", description = "로그인 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "로그인에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "로그인이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "로그인에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @@ -75,254 +31,44 @@ public LoginController(Environment environment, }) @PostMapping("/login") public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ - - logger.info("CALL /api/v1/auth/login"); - logger.info("[input] API_SECRET_KEY={}", loginRequestDTO.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if (loginRequestDTO.getApiSecretKey().equals(API_SECRET_KEY)) { - - if (inputValidate.isEmpty(loginRequestDTO.getUserId()) || inputValidate.isEmpty(loginRequestDTO.getPassword())) { - throw new LoginException("101", "로그인이 불가능합니다."); - } else { - - logger.info("@@@ userId={}", loginRequestDTO.getUserId()); - logger.info("@@@ password={}", loginRequestDTO.getPassword()); - - // 로그인 성공 -> 사용자 정보 조회(membershipNo, userId 등 토큰에 넣을 값) - Member member = memberService.selectUserMembershipInfo(loginRequestDTO.getUserId()); - if (null == member) { - throw new LoginException("102", "회원 정보가 존재하지 않습니다."); - } - - boolean ok = passwordEncoder.matches(loginRequestDTO.getPassword(), member.getPassword()); - - logger.info("@@ ok={}", ok); - if(!ok) { - throw new LoginException("103", "로그인에 실패하였습니다."); - } - - String userId = member.getUserId(); - - // ACCESS, REFRESH TOKEN 생싱 & REFRESH TOKEN 저장 - LoginResponse loginResponse = authService.loginAfterSignup(userId, "web-basic"); - Map loginResponseMap = Map.of( - "accessToken", loginResponse.tokens().accessToken(), - "accessTokenExpiresIn", loginResponse.tokens().accessTokenExpiresIn(), - "userId", userId, - "membershipNo", loginResponse.membershipNo(), - "refreshToken", loginResponse.tokens().refreshToken(), - "refreshTokenExpiresIn", loginResponse.tokens().refreshTokenExpiresIn() - ); - - resultCode = "200"; - message = "로그인에 성공하였습니다."; - apiResponse.setData(loginResponseMap); - } - - } else { - throw new LoginException("100", "잘못된 접근입니다."); - } - } catch (LoginException ex) { - resultCode = ex.getCode(); - message = ex.getMessage(); - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return loginService.login(loginRequestDTO); } @Operation(summary = "아이디 찾기 기능", description = "아이디 찾기 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 전화번호로 가입된 아이디입니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 전화번호로 가입된 아이디가 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "해당 전화번호로 가입된 계정이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/find-user") - public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequestDTO){ - - logger.info("CALL /api/v1/auth/find-user"); - logger.info("[input] API_SECRET_KEY={}", searchUserIdRequestDTO.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(searchUserIdRequestDTO.getApiSecretKey().equals(API_SECRET_KEY)){ - - if(inputValidate.isEmpty(searchUserIdRequestDTO.getTel())){ - resultCode = "101"; - message = "전화번호가 존재하지 않습니다."; - - } else { - - searchUserIdRequestDTO.setTel(encryptUtil.encrypt(searchUserIdRequestDTO.getTel())); - logger.info("tel={}", searchUserIdRequestDTO.getTel()); - List myUserIdList = loginService.myUserIdList(searchUserIdRequestDTO); - - int userIdCnt = myUserIdList.size(); - if(userIdCnt > 0){ - resultCode = "200"; - message = "해당 전화번호로 가입된 아이디입니다."; - - List> userIdList = new ArrayList<>(); - for(UserIdDTO vo : myUserIdList){ - Map map = new HashMap<>(); - map.put("userId", vo.getUserId()); - userIdList.add(map); - } - apiResponse.setData(userIdList); - - } else { - resultCode = "201"; - message = "해당 전화번호로 가입된 계정이 존재하지 않습니다."; - } - } - - } else{ - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + public ApiResponse findUser(@RequestBody SearchUserIdDTO searchUserIdRequestDTO){ + return loginService.findUser(searchUserIdRequestDTO); } @Operation(summary = "비밀번호 재설정 기능", description = "비밀번호 재설정 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "비밀번호 재설정에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "아이디, 비밀번호 값이 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "비밀번호 재설정에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/password-reset/confirm") public ApiResponse updatePassword(@RequestBody LoginDTO vo){ - - logger.info("CALL /api/v1/auth/password-reset/confirm"); - logger.info("[input] API_SECRET_KEY={}", vo.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(vo.getApiSecretKey().equals(API_SECRET_KEY)){ - - if(inputValidate.isEmpty(vo.getUserId()) || inputValidate.isEmpty(vo.getPassword())){ - resultCode = "101"; - message = "아이디, 비밀번호 값이 없습니다."; - - } else { - // 비밀번호 암호화 - String encodedPassword = passwordConfig.passwordEncoder().encode(vo.getPassword()); - vo.setPassword(encodedPassword); - - // 비밀번호 update - int updatePassword = loginService.updatePassword(vo); - logger.info("@@ updatePassword={}", updatePassword); - - if(updatePassword > 0){ - resultCode = "200"; - message = "비밀번호 재설정에 성공하였습니다."; - } else{ - resultCode = "300"; - message = "비밀번호 재설정에 실패하였습니다."; - } - } - - } else{ - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return loginService.updatePasswordProcess(vo); } @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") @PostMapping("/token/refresh") - public ResponseEntity> refresh( - @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body - ) { - - logger.info("CALL /api/v1/auth/token/refresh"); - - try { - String rt = Optional.ofNullable(refreshHeader) - .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) - .orElse(null); - - if (rt == null || rt.isBlank()) { - return ResponseEntity.status(401).body(Map.of("error", "refresh_required")); - } - - TokenPair pair = authService.rotate(rt); // ❗️핵심 로직 (아래 2) 참조) - - return ResponseEntity.ok(Map.of( - "accessToken", pair.accessToken(), - "accessTokenExpiresIn", pair.accessTokenExpiresIn(), - "refreshToken", pair.refreshToken(), - "refreshTokenExpiresIn", pair.refreshTokenExpiresIn() - )); - - } catch (InvalidRefreshTokenException e) { - return ResponseEntity.status(HttpServletResponse.SC_UNAUTHORIZED) - .body(Map.of( - "resultCode", "400", - "errorCode", e.getCode(), - "message", e.getMessage(), - "needLogin", true - )); - } + public ApiResponse refresh(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { + return loginService.refreshToken(refreshTokenRequestDTO); } - /** - * 현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE - * 입력 경로: - * - 헤더: Refresh-Token: - * - 바디: { "refreshToken": "" } - */ @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") @PostMapping("/logout") - public ResponseEntity> logout( - @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body - ) { - String refresh = refreshHeader; - if ((refresh == null || refresh.isBlank()) && body != null) { - refresh = body.get("refreshToken"); - } - if (refresh != null && !refresh.isBlank()) { - authService.logout(refresh); // DB REVOKE - } - return ResponseEntity.ok(Map.of("resultCode", "200", "message", "로그아웃 되었습니다.")); + public ApiResponse logout(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { + return loginService.logout(refreshTokenRequestDTO); } } diff --git a/src/main/java/com/barogagi/member/login/dto/RefreshTokenRequestDTO.java b/src/main/java/com/barogagi/member/login/dto/RefreshTokenRequestDTO.java new file mode 100644 index 0000000..9a573d4 --- /dev/null +++ b/src/main/java/com/barogagi/member/login/dto/RefreshTokenRequestDTO.java @@ -0,0 +1,12 @@ +package com.barogagi.member.login.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RefreshTokenRequestDTO { + + private String refreshToken = ""; + +} diff --git a/src/main/java/com/barogagi/member/login/dto/TokenPair.java b/src/main/java/com/barogagi/member/login/dto/TokenPair.java index 7554a95..8e3da4e 100644 --- a/src/main/java/com/barogagi/member/login/dto/TokenPair.java +++ b/src/main/java/com/barogagi/member/login/dto/TokenPair.java @@ -2,5 +2,6 @@ public record TokenPair( String accessToken, long accessTokenExpiresIn, - String refreshToken, long refreshTokenExpiresIn + String refreshToken, long refreshTokenExpiresIn, + String resultCode, String message ) {} diff --git a/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java b/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java index c330e3e..2c89470 100644 --- a/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java +++ b/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java @@ -1,14 +1,12 @@ package com.barogagi.member.login.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class InvalidRefreshTokenException extends RuntimeException{ +public class InvalidRefreshTokenException extends BusinessException { - private final String code; - - public InvalidRefreshTokenException(String code, String message) { - super(message); - this.code = code; + public InvalidRefreshTokenException(String resultCode, String message) { + super(resultCode, message); } } diff --git a/src/main/java/com/barogagi/member/login/exception/LoginException.java b/src/main/java/com/barogagi/member/login/exception/LoginException.java index 3dda611..84a44f9 100644 --- a/src/main/java/com/barogagi/member/login/exception/LoginException.java +++ b/src/main/java/com/barogagi/member/login/exception/LoginException.java @@ -1,13 +1,12 @@ package com.barogagi.member.login.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class LoginException extends RuntimeException { - private final String code; +public class LoginException extends BusinessException { - public LoginException(String code, String message) { - super(message); - this.code = code; + public LoginException(String resultCode, String message) { + super(resultCode, message); } } diff --git a/src/main/java/com/barogagi/member/login/service/AuthService.java b/src/main/java/com/barogagi/member/login/service/AuthService.java index 78474c2..148df4b 100644 --- a/src/main/java/com/barogagi/member/login/service/AuthService.java +++ b/src/main/java/com/barogagi/member/login/service/AuthService.java @@ -1,5 +1,6 @@ package com.barogagi.member.login.service; +import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.login.dto.*; import com.barogagi.member.login.entity.RefreshToken; import com.barogagi.member.login.entity.UserMembership; @@ -76,14 +77,21 @@ public LoginResponse login(LoginRequest req) { refreshRepo.save(rt); return new LoginResponse( - new TokenPair(access, accessExp, refresh, refreshExp), + new TokenPair( + access, + accessExp, + refresh, + refreshExp, + ProcessResultCode.SUCCESS_LOGIN.getResultCode(), + ProcessResultCode.SUCCESS_LOGIN.getMessage() + ), no, u.getUserId(), u.getJoinType() ); } /** 구글/네이버 등 OAuth 가입 직후: userId로 바로 토큰 발급 (비밀번호 검증 없음) */ public LoginResponse loginAfterSignup(String userId, String deviceId) { - var u = userRepo.findByUserId(userId) + UserMembership u = userRepo.findByUserId(userId) .orElseThrow(() -> new RuntimeException("USER_NOT_FOUND")); String no = u.getMembershipNo(); @@ -91,7 +99,7 @@ public LoginResponse loginAfterSignup(String userId, String deviceId) { String refresh = jwt.generateRefreshToken(no, deviceId != null ? deviceId : "web-oauth"); // Refresh 저장(VALID) - var rt = new RefreshToken(); + RefreshToken rt = new RefreshToken(); rt.setMembershipNo(no); rt.setDeviceId(deviceId != null ? deviceId : "web-oauth"); rt.setToken(refresh); @@ -101,13 +109,21 @@ public LoginResponse loginAfterSignup(String userId, String deviceId) { refreshRepo.save(rt); return new LoginResponse( - new TokenPair(access, accessExp, refresh, refreshExp), + new TokenPair( + access, + accessExp, + refresh, + refreshExp, + ProcessResultCode.SUCCESS_REFRESH_TOKEN.getResultCode(), + ProcessResultCode.SUCCESS_REFRESH_TOKEN.getMessage() + ), no, u.getUserId(), u.getJoinType() ); } @Transactional public TokenPair rotate(String refreshToken) { + try { if (!jwt.isTokenValid(refreshToken) || !jwt.isRefreshToken(refreshToken)) { throw new BadCredentialsException("invalid_refresh_token"); @@ -116,6 +132,9 @@ public TokenPair rotate(String refreshToken) { throw new BadCredentialsException("invalid_refresh_token"); } + String newAccess = ""; + String newRefresh = ""; + String membershipNo = jwt.getMembershipNo(refreshToken); String deviceId = jwt.getDeviceId(refreshToken); @@ -124,46 +143,67 @@ public TokenPair rotate(String refreshToken) { deviceId = "web-oauth"; } - // 현재 리프레시가 DB에 VALID로 존재하는지 확인 - RefreshToken current = refreshRepo.findByTokenAndStatus( - refreshToken, "VALID" - ).orElseThrow(() -> new InvalidRefreshTokenException("refresh_not_found_or_revoked", "로그인을 진행해주세요.")); + try { - // 만료 체크 - if (current.getExpiresAt().isBefore(LocalDateTime.now())) { - current.setStatus("REVOKED"); - refreshRepo.save(current); - throw new InvalidRefreshTokenException("refresh_expired", "로그인을 다시 진행해주세요."); - } + // 현재 리프레시가 DB에 VALID로 존재하는지 확인 + RefreshToken current = refreshRepo.findByTokenAndStatus(refreshToken, "VALID") + .orElseThrow(() -> new InvalidRefreshTokenException( + ProcessResultCode.REQUIRED_LOGIN.getResultCode(), + ProcessResultCode.REQUIRED_LOGIN.getMessage() + )); + + // 만료 체크 + if (current.getExpiresAt().isBefore(LocalDateTime.now())) { + current.setStatus("REVOKED"); + refreshRepo.save(current); + throw new InvalidRefreshTokenException( + ProcessResultCode.REQUIRED_RE_LOGIN.getResultCode(), + ProcessResultCode.REQUIRED_RE_LOGIN.getMessage() + ); + } - // 같은 멤버/디바이스의 기존 VALID 토큰들 모두 REVOKE (동시 세션 차단용) - var olds = refreshRepo.findByMembershipNoAndDeviceIdAndStatus( - membershipNo, deviceId, "VALID" - ); - for (var o : olds) { - o.setStatus("REVOKED"); + // 같은 멤버/디바이스의 기존 VALID 토큰들 모두 REVOKE (동시 세션 차단용) + List olds = refreshRepo.findByMembershipNoAndDeviceIdAndStatus(membershipNo, deviceId, "VALID"); + for (RefreshToken o : olds) { + o.setStatus("REVOKED"); + } + refreshRepo.saveAll(olds); + + // 새 토큰 발급 + UserMembership user = userRepo.findById(membershipNo) + .orElseThrow(() -> new InvalidRefreshTokenException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + )); + + newAccess = jwt.generateAccessToken(membershipNo, user.getUserId()); + newRefresh = jwt.generateRefreshToken(membershipNo, deviceId); + + RefreshToken next = new RefreshToken(); + next.setMembershipNo(membershipNo); + next.setDeviceId(deviceId); + next.setToken(newRefresh); + next.setStatus("VALID"); + next.setCreatedAt(LocalDateTime.now()); + next.setExpiresAt(LocalDateTime.now().plusSeconds(jwt.getRefreshExpSeconds())); + refreshRepo.save(next); + + } catch (InvalidRefreshTokenException ex) { + return new TokenPair( + "", + 0, + "", + 0, + ex.getResultCode(), + ex.getMessage() + ); } - refreshRepo.saveAll(olds); - - // 새 토큰 발급 - var user = userRepo.findById(membershipNo) - .orElseThrow(() -> new InvalidRefreshTokenException("user_not_found", "회원 정보가 존재하지 않습니다.")); - - String newAccess = jwt.generateAccessToken(membershipNo, user.getUserId()); - String newRefresh = jwt.generateRefreshToken(membershipNo, deviceId); - - RefreshToken next = new RefreshToken(); - next.setMembershipNo(membershipNo); - next.setDeviceId(deviceId); - next.setToken(newRefresh); - next.setStatus("VALID"); - next.setCreatedAt(LocalDateTime.now()); - next.setExpiresAt(LocalDateTime.now().plusSeconds(jwt.getRefreshExpSeconds())); - refreshRepo.save(next); return new TokenPair( newAccess, jwt.getAccessExpSeconds(), - newRefresh, jwt.getRefreshExpSeconds() + newRefresh, jwt.getRefreshExpSeconds(), + ProcessResultCode.SUCCESS_REFRESH_TOKEN.getResultCode(), + ProcessResultCode.SUCCESS_REFRESH_TOKEN.getMessage() ); } @@ -178,7 +218,7 @@ public void logout(String refreshToken) { List tokens = refreshRepo .findByMembershipNoAndDeviceIdAndStatus(membershipNo, deviceId, "VALID"); - for (var t : tokens) t.setStatus("REVOKED"); + for (RefreshToken t : tokens) t.setStatus("REVOKED"); if (!tokens.isEmpty()) refreshRepo.saveAll(tokens); } @@ -218,7 +258,7 @@ public Map selectUserInfoByToken(String refreshToken) { returnMap.put("membershipNo", membershipNo); } catch (InvalidRefreshTokenException e) { - resultCode = e.getCode(); + resultCode = e.getResultCode(); message = e.getMessage(); } finally { returnMap.put("resultCode", resultCode); diff --git a/src/main/java/com/barogagi/member/login/service/LoginService.java b/src/main/java/com/barogagi/member/login/service/LoginService.java index 29916a7..8300cc5 100644 --- a/src/main/java/com/barogagi/member/login/service/LoginService.java +++ b/src/main/java/com/barogagi/member/login/service/LoginService.java @@ -1,23 +1,248 @@ package com.barogagi.member.login.service; -import com.barogagi.member.login.dto.LoginDTO; -import com.barogagi.member.login.dto.LoginVO; -import com.barogagi.member.login.dto.SearchUserIdDTO; -import com.barogagi.member.login.dto.UserIdDTO; +import com.barogagi.config.PasswordConfig; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.member.info.dto.Member; +import com.barogagi.member.info.service.MemberService; +import com.barogagi.member.login.dto.*; +import com.barogagi.member.login.exception.LoginException; import com.barogagi.member.login.mapper.LoginMapper; +import com.barogagi.response.ApiResponse; +import com.barogagi.util.EncryptUtil; +import com.barogagi.util.InputValidate; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.Validator; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Service public class LoginService { private final LoginMapper loginMapper; + private final Validator validator; + private final InputValidate inputValidate; + private final EncryptUtil encryptUtil; + private final PasswordConfig passwordConfig; + private final MemberService memberService; + private final PasswordEncoder passwordEncoder; + private final AuthService authService; @Autowired - public LoginService(LoginMapper loginMapper){ + public LoginService( + LoginMapper loginMapper, + Validator validator, + InputValidate inputValidate, + EncryptUtil encryptUtil, + PasswordConfig passwordConfig, + MemberService memberService, + PasswordEncoder passwordEncoder, + AuthService authService + ) + { this.loginMapper = loginMapper; + this.validator = validator; + this.inputValidate = inputValidate; + this.encryptUtil = encryptUtil; + this.passwordConfig = passwordConfig; + this.memberService = memberService; + this.passwordEncoder = passwordEncoder; + this.authService = authService; + } + + public ApiResponse findUser(SearchUserIdDTO searchUserIdDTO) { + + String resultCode = ""; + String message = ""; + List userIdList = null; + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(searchUserIdDTO.getApiSecretKey())) { + throw new LoginException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(searchUserIdDTO.getTel())) { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + searchUserIdDTO.setTel(encryptUtil.encrypt(searchUserIdDTO.getTel())); + List searchIdList = this.myUserIdList(searchUserIdDTO); + + if(searchIdList.isEmpty()) { + resultCode = ProcessResultCode.NOT_FOUND_ACCOUNT.getResultCode(); + message = ProcessResultCode.NOT_FOUND_ACCOUNT.getMessage(); + } else { + resultCode = ProcessResultCode.FOUND_ACCOUNT.getResultCode(); + message = ProcessResultCode.FOUND_ACCOUNT.getMessage(); + userIdList = searchIdList; + } + + return ApiResponse.resultData(userIdList, resultCode, message); + } + + public ApiResponse updatePasswordProcess(LoginDTO loginDTO) { + String resultCode = ""; + String message = ""; + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(loginDTO.getApiSecretKey())) { + throw new LoginException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if ( + inputValidate.isEmpty(loginDTO.getUserId()) + || inputValidate.isEmpty(loginDTO.getPassword()) + ) { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 비밀번호 암호화 + loginDTO.setPassword(passwordEncoder.encode(loginDTO.getPassword())); + + // 4. 비밀번호 update + int updatePassword = this.updatePassword(loginDTO); + if(updatePassword > 0) { + resultCode = ProcessResultCode.SUCCESS_UPDATE_PASSWORD.getResultCode(); + message = ProcessResultCode.SUCCESS_UPDATE_PASSWORD.getMessage(); + } else { + throw new LoginException( + ProcessResultCode.FAIL_UPDATE_PASSWORD.getResultCode(), + ProcessResultCode.FAIL_UPDATE_PASSWORD.getMessage() + ); + } + + return ApiResponse.result(resultCode, message); + } + + public ApiResponse login(LoginDTO loginDTO) { + + String resultCode = ""; + String message = ""; + Map dataMap = new HashMap<>(); + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(loginDTO.getApiSecretKey())) { + throw new LoginException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if( + inputValidate.isEmpty(loginDTO.getUserId()) + || inputValidate.isEmpty(loginDTO.getPassword()) + ) + { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 아이디로 회원정보 조회 + Member member = memberService.selectUserMembershipInfo(loginDTO.getUserId()); + if (null == member) { + throw new LoginException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + ); + } + + // 4. 비밀번호 일치 여부 + boolean ok = passwordEncoder.matches(loginDTO.getPassword(), member.getPassword()); + if(!ok) { + throw new LoginException( + ProcessResultCode.FAIL_LOGIN.getResultCode(), + ProcessResultCode.FAIL_LOGIN.getMessage() + ); + } + + // 5. ACCESS, REFRESH TOKEN 생성 & REFRESH TOKEN 저장 + LoginResponse loginResponse = authService.loginAfterSignup(member.getUserId(), "web-basic"); + + resultCode = loginResponse.tokens().resultCode(); + message = loginResponse.tokens().message(); + + dataMap = Map.of( + "accessToken", loginResponse.tokens().accessToken(), + "accessTokenExpiresIn", loginResponse.tokens().accessTokenExpiresIn(), + "userId", member.getUserId(), + "membershipNo", loginResponse.membershipNo(), + "refreshToken", loginResponse.tokens().refreshToken(), + "refreshTokenExpiresIn", loginResponse.tokens().refreshTokenExpiresIn() + ); + + return ApiResponse.resultData(dataMap, resultCode, message); + } + + public ApiResponse refreshToken(RefreshTokenRequestDTO refreshTokenRequestDTO) { + + String resultCode = ""; + String message = ""; + Map data = new HashMap<>(); + + // 1. 필수 입력값 확인 + if (inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 2. ACCESS, REFRESH TOKEN 재생성 + TokenPair pair = authService.rotate(refreshTokenRequestDTO.getRefreshToken()); + + resultCode = pair.resultCode(); + message = pair.message(); + + if(!resultCode.equals("200")) { + throw new LoginException(resultCode, message); + } + + data.put("accessToken", pair.accessToken()); + data.put("accessTokenExpiresIn", pair.accessTokenExpiresIn()); + data.put("refreshToken", pair.refreshToken()); + data.put("refreshTokenExpiresIn", pair.refreshTokenExpiresIn()); + + return ApiResponse.resultData(data, resultCode, message); + } + + public ApiResponse logout(RefreshTokenRequestDTO refreshTokenRequestDTO) { + + // 1. 필수 입력값 확인 + if(inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 2. 로그아웃 + authService.logout(refreshTokenRequestDTO.getRefreshToken()); // DB REVOKE + + return ApiResponse.result( + ProcessResultCode.SUCCESS_LOGOUT.getResultCode(), + ProcessResultCode.SUCCESS_LOGOUT.getMessage() + ); } public int selectMemberCnt(LoginDTO loginDTO){ @@ -28,13 +253,9 @@ public LoginVO findByUserId(LoginDTO loginDTO) { return loginMapper.findByUserId(loginDTO); } - public List myUserIdList(SearchUserIdDTO searchUserIdDTO){ - return loginMapper.myUserIdList(searchUserIdDTO); - } + public List myUserIdList(SearchUserIdDTO searchUserIdDTO){ return loginMapper.myUserIdList(searchUserIdDTO);} - public int updatePassword(LoginDTO loginDTO){ - return loginMapper.updatePassword(loginDTO); - } + public int updatePassword(LoginDTO loginDTO){return loginMapper.updatePassword(loginDTO);} public LoginVO findMembershipNo(LoginVO vo) { return loginMapper.findMembershipNo(vo); diff --git a/src/main/java/com/barogagi/response/ApiResponse.java b/src/main/java/com/barogagi/response/ApiResponse.java index f5bd553..9b773b2 100644 --- a/src/main/java/com/barogagi/response/ApiResponse.java +++ b/src/main/java/com/barogagi/response/ApiResponse.java @@ -30,4 +30,19 @@ public static ApiResponse error(String code, String message) { res.message = message; return res; } + + public static ApiResponse resultData(T data, String code, String message) { + ApiResponse res = new ApiResponse<>(); + res.resultCode = code; + res.message = message; + res.data = data; + return res; + } + + public static ApiResponse result(String code, String message) { + ApiResponse res = new ApiResponse<>(); + res.resultCode = code; + res.message = message; + return res; + } } diff --git a/src/main/java/com/barogagi/terms/controller/TermsController.java b/src/main/java/com/barogagi/terms/controller/TermsController.java index f726d43..2612f24 100644 --- a/src/main/java/com/barogagi/terms/controller/TermsController.java +++ b/src/main/java/com/barogagi/terms/controller/TermsController.java @@ -1,168 +1,44 @@ package com.barogagi.terms.controller; -import com.barogagi.member.login.dto.LoginVO; -import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; import com.barogagi.terms.dto.*; import com.barogagi.terms.service.TermsService; -import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; -import java.util.List; - @Tag(name = "약관", description = "약관 관련 API") @RestController @RequestMapping("/api/v1/terms") +@RequiredArgsConstructor public class TermsController { - private static final Logger logger = LoggerFactory.getLogger(TermsController.class); - private final InputValidate inputValidate; private final TermsService termsService; - private final LoginService loginService; - - private final String API_SECRET_KEY; - - @Autowired - public TermsController(Environment environment, InputValidate inputValidate, - TermsService termsService, LoginService loginService){ - this.inputValidate = inputValidate; - this.termsService = termsService; - this.loginService = loginService; - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); - } @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.
회원가입 시 사용할 경우 termsType 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 조회에 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "조회하실 약관의 종류 값이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "약관이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping public ApiResponse termsList(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String termsType){ - logger.info("CALL /api/v1/terms"); - logger.info("[input] API_SECRET_KEY={}", apiSecretKey); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(apiSecretKey.equals(API_SECRET_KEY)) { - - if(inputValidate.isEmpty(termsType)) { - resultCode = "101"; - message = "조회하실 약관의 종류 값이 존재하지 않습니다."; - } else { - TermsInputDTO termsInputDTO = new TermsInputDTO(); - termsInputDTO.setTermsType(termsType); - List termsList = termsService.selectTermsList(termsInputDTO); - - int termsCnt = termsList.size(); - logger.info("termsCnt={}", termsCnt); - if(termsCnt > 0) { - resultCode = "200"; - message = "약관 조회에 성공하였습니다."; - apiResponse.setData(termsList); - - } else { - resultCode = "102"; - message = "약관이 존재하지 않습니다."; - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return termsService.termsListProcess(apiSecretKey, termsType); } @Operation(summary = "약관 동의 여부 저장", description = "약관 동의 여부 저장 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 저장에 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "해당 사용자의 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "해당 사용자의 정보가 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "약관 저장에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/terms-agreements") public ApiResponse insertTermsAgree(@RequestBody TermsDTO termsDTO) { - logger.info("CALL /api/v1/terms/{userId}/terms-agreements"); - logger.info("[input] API_SECRET_KEY={}", termsDTO.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(termsDTO.getApiSecretKey().equals(API_SECRET_KEY)) { - - String userId = termsDTO.getUserId(); - LoginVO lvo = new LoginVO(); - lvo.setUserId(userId); - - LoginVO loginVO = loginService.findMembershipNo(lvo); - if(null != loginVO) { - - List termsAgreeDTOList = new ArrayList<>(); - List termsAgreeList = termsDTO.getTermsAgreeList(); - - for(TermsProcessDTO termsProcessDTO : termsAgreeList) { - - TermsAgreeDTO termsAgreeDTO = new TermsAgreeDTO(); - termsAgreeDTO.setMembershipNo(loginVO.getMembershipNo()); - termsAgreeDTO.setTermsNum(termsProcessDTO.getTermsNum()); - termsAgreeDTO.setAgreeYn(termsProcessDTO.getAgreeYn()); - - termsAgreeDTOList.add(termsAgreeDTO); - } - String resCode = termsService.insertTermsAgreeList(termsAgreeDTOList); - if(resCode.equals("200")) { - resultCode = "200"; - message = "약관 저장에 성공하였습니다."; - } else { - resultCode = "300"; - message = "약관 저장에 실패하였습니다."; - } - - } else { - resultCode = "101"; - message = "해당 사용자의 정보가 존재하지 않습니다."; - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return termsService.termsAgreementsProcess(termsDTO); } } diff --git a/src/main/java/com/barogagi/terms/exception/TermsException.java b/src/main/java/com/barogagi/terms/exception/TermsException.java new file mode 100644 index 0000000..9ecb1a7 --- /dev/null +++ b/src/main/java/com/barogagi/terms/exception/TermsException.java @@ -0,0 +1,12 @@ +package com.barogagi.terms.exception; + +import com.barogagi.config.exception.BusinessException; +import lombok.Getter; + +@Getter +public class TermsException extends BusinessException { + + public TermsException(String resultCode, String message) { + super(resultCode, message); + } +} diff --git a/src/main/java/com/barogagi/terms/service/TermsService.java b/src/main/java/com/barogagi/terms/service/TermsService.java index 6e49b3d..3e16b0f 100644 --- a/src/main/java/com/barogagi/terms/service/TermsService.java +++ b/src/main/java/com/barogagi/terms/service/TermsService.java @@ -1,34 +1,145 @@ package com.barogagi.terms.service; -import com.barogagi.terms.dto.TermsAgreeDTO; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.member.login.dto.LoginVO; +import com.barogagi.member.login.service.LoginService; +import com.barogagi.response.ApiResponse; +import com.barogagi.terms.dto.*; +import com.barogagi.terms.exception.TermsException; import com.barogagi.terms.mapper.TermsMapper; -import com.barogagi.terms.dto.TermsInputDTO; -import com.barogagi.terms.dto.TermsOutputDTO; -import com.barogagi.terms.dto.TermsProcessDTO; +import com.barogagi.util.InputValidate; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.Validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; +import java.util.ArrayList; import java.util.List; @Service public class TermsService { - private TermsMapper termsMapper; + private final TermsMapper termsMapper; + private final Validator validator; + private final InputValidate inputValidate; + private final LoginService loginService; @Autowired - public TermsService(TermsMapper termsMapper) { + public TermsService( + TermsMapper termsMapper, + Validator validator, + InputValidate inputValidate, + LoginService loginService + ) + { this.termsMapper = termsMapper; + this.validator = validator; + this.inputValidate = inputValidate; + this.loginService = loginService; + } + + public ApiResponse termsListProcess(String apiSecretKey, String termsType) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new TermsException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(termsType)) { + throw new TermsException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 약관 조회 + TermsInputDTO termsInputDTO = new TermsInputDTO(); + termsInputDTO.setTermsType(termsType); + List termsList = this.selectTermsList(termsInputDTO); + + if(termsList.isEmpty()) { + throw new TermsException( + ProcessResultCode.NOT_FOUND_TERMS.getResultCode(), + ProcessResultCode.NOT_FOUND_TERMS.getMessage() + ); + + } + + return ApiResponse.resultData( + termsList, + ProcessResultCode.FOUND_TERMS.getResultCode(), + ProcessResultCode.FOUND_TERMS.getMessage() + ); + } + + public ApiResponse termsAgreementsProcess(TermsDTO termsDTO) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(termsDTO.getApiSecretKey())) { + throw new TermsException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(termsDTO.getUserId()) || + termsDTO.getTermsAgreeList() == null || + termsDTO.getTermsAgreeList().isEmpty()) { + throw new TermsException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + LoginVO lvo = new LoginVO(); + lvo.setUserId(termsDTO.getUserId()); + LoginVO loginVO = loginService.findMembershipNo(lvo); + if(null == loginVO) { + throw new TermsException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + ); + } + + List termsAgreeDTOList = new ArrayList<>(); + List termsAgreeList = termsDTO.getTermsAgreeList(); + + for(TermsProcessDTO termsProcessDTO : termsAgreeList) { + TermsAgreeDTO termsAgreeDTO = new TermsAgreeDTO(); + termsAgreeDTO.setMembershipNo(loginVO.getMembershipNo()); + termsAgreeDTO.setTermsNum(termsProcessDTO.getTermsNum()); + termsAgreeDTO.setAgreeYn(termsProcessDTO.getAgreeYn()); + termsAgreeDTOList.add(termsAgreeDTO); + } + String resCode = this.insertTermsAgreeList(termsAgreeDTOList); + + if(!resCode.equals("200")) { + throw new TermsException( + ProcessResultCode.FAIL_INSERT_TERMS.getResultCode(), + ProcessResultCode.FAIL_INSERT_TERMS.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_INSERT_TERMS.getResultCode(), + ProcessResultCode.SUCCESS_INSERT_TERMS.getMessage() + ); } // 사용중인 약관 목록 조회 - public List selectTermsList(TermsInputDTO termsInputDTO) throws Exception { + public List selectTermsList(TermsInputDTO termsInputDTO) { return termsMapper.selectTermsList(termsInputDTO); } // 약관 동의 여부 저장 - public int insertTermsAgreeInfo(TermsAgreeDTO vo) throws Exception { + public int insertTermsAgreeInfo(TermsAgreeDTO vo) { return termsMapper.insertTermsAgreeInfo(vo); } diff --git a/src/main/java/com/barogagi/util/MembershipUtil.java b/src/main/java/com/barogagi/util/MembershipUtil.java index ab18258..e9b4ebf 100644 --- a/src/main/java/com/barogagi/util/MembershipUtil.java +++ b/src/main/java/com/barogagi/util/MembershipUtil.java @@ -1,5 +1,6 @@ package com.barogagi.util; +import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.exception.BasicException; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; @@ -21,12 +22,15 @@ public Map membershipNoService(HttpServletRequest request) { try { Object membershipNoAttr = request.getAttribute("membershipNo"); if(membershipNoAttr == null) { - throw new BasicException("401", "접근 권한이 존재하지 않습니다."); + throw new BasicException( + ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), + ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() + ); } membershipNo = String.valueOf(membershipNoAttr); - resultCode = "200"; - message = "회원 번호가 존재합니다."; + resultCode = ResultCode.EXIST_ACCESS_AUTH.getResultCode(); + message = ResultCode.EXIST_ACCESS_AUTH.getMessage(); } catch (BasicException ex) { resultCode = ex.getResultCode(); @@ -39,4 +43,17 @@ public Map membershipNoService(HttpServletRequest request) { return resultMap; } + + public String selectMembershipNo(HttpServletRequest request) { + + Object membershipNoAttr = request.getAttribute("membershipNo"); + if(null == membershipNoAttr) { + throw new BasicException( + ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), + ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() + ); + } + + return String.valueOf(membershipNoAttr); + } } diff --git a/src/main/java/com/barogagi/util/Validator.java b/src/main/java/com/barogagi/util/Validator.java index e81a49b..5949a3b 100644 --- a/src/main/java/com/barogagi/util/Validator.java +++ b/src/main/java/com/barogagi/util/Validator.java @@ -1,5 +1,7 @@ package com.barogagi.util; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; import java.util.regex.Pattern; @@ -10,6 +12,18 @@ public class Validator { // 금칙어 목록 예시 (확장 가능) private static final String[] BLOCKED_WORDS = {"admin", "운영자"}; + private final String API_SECRET_KEY; + + @Autowired + public Validator(Environment environment) { + this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + } + + // API SECRET KEY 검증 + public boolean apiSecretKeyCheck(String apiSecretKey) { + return apiSecretKey.equals(API_SECRET_KEY); + } + // 아이디 검증 public boolean isValidId(String userId) { diff --git a/src/main/java/com/barogagi/util/exception/BasicException.java b/src/main/java/com/barogagi/util/exception/BasicException.java index 198ad27..814c567 100644 --- a/src/main/java/com/barogagi/util/exception/BasicException.java +++ b/src/main/java/com/barogagi/util/exception/BasicException.java @@ -1,23 +1,24 @@ package com.barogagi.util.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class BasicException extends RuntimeException { +public class BasicException extends BusinessException { private final String resultCode; private final ErrorCode errorCode; // 신규 생성자 public BasicException(ErrorCode errorCode) { - super(errorCode.getMessage()); + super(errorCode.getCode(), errorCode.getMessage()); this.errorCode = errorCode; this.resultCode = errorCode.getCode(); } // 기존 생성자 public BasicException(String resultCode, String message) { - super(message); + super(resultCode, message); this.resultCode = resultCode; errorCode = null; } diff --git a/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java index e897815..db6f7ea 100644 --- a/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package com.barogagi.util.exception; +import com.barogagi.config.exception.BusinessException; import com.barogagi.response.ApiResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -31,4 +32,9 @@ public ResponseEntity> handleUnknown(Exception e) { ErrorCode.INTERNAL_ERROR.getMessage() )); } + + @ExceptionHandler(BusinessException.class) + public ApiResponse handleBusinessException(BusinessException e) { + return ApiResponse.result(e.getResultCode(), e.getMessage()); + } } diff --git a/src/main/resources/mapper/JoinMapper.xml b/src/main/resources/mapper/JoinMapper.xml index 8acef38..4b73d5e 100644 --- a/src/main/resources/mapper/JoinMapper.xml +++ b/src/main/resources/mapper/JoinMapper.xml @@ -8,7 +8,7 @@ ]]> - - Date: Mon, 22 Dec 2025 12:08:01 +0900 Subject: [PATCH 56/79] =?UTF-8?q?FEAT=20:=20=EB=A1=9C=EA=B7=B8=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=ED=99=94=20&=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20(#39)=20(#40)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FEAT] : 로그 공통화 작업중 & 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 일반 로그인 & 토큰 재발급 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 약관 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 회원정보 코드 리팩토링 중 * [FEAT] : 로그 공통화 작업중 & 회원정보 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 메인 화면 api 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 인증 api 코드 리팩토링 * [FEAT] : 필요없는 import 삭제 * [FEAT] : 필요없는 import 삭제 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 --- .../controller/ApprovalController.java | 171 +--------- .../approval/exception/ApprovalException.java | 12 + .../approval/service/ApprovalService.java | 138 +++++++- .../com/barogagi/config/JwtAuthFilter.java | 44 +-- .../config/OAuth2LoginSuccessHandler.java | 2 + .../com/barogagi/config/SecurityConfig.java | 12 +- .../config/exception/BusinessException.java | 14 + .../config/resultCode/ProcessResultCode.java | 82 +++++ .../config/resultCode/ResultCode.java | 23 ++ .../logging/ControllerLoggingAspect.java | 27 ++ .../logging/ServiceLoggingAspect.java | 44 +++ .../controller/MainPageController.java | 172 +--------- .../mainPage/exception/MainPageException.java | 8 +- .../mainPage/response/MainPageResponse.java | 20 ++ .../mainPage/service/MainPageService.java | 127 ++++++- .../basic/join/controller/JoinController.java | 317 ++---------------- .../basic/join/exception/JoinException.java | 12 + .../member/basic/join/mapper/JoinMapper.java | 4 +- .../basic/join/service/BasicJoinService.java | 285 ++++++++++++++++ .../basic/join/service/JoinService.java | 102 +++--- .../info/controller/InfoController.java | 158 +-------- .../info/exception/MemberInfoException.java | 7 +- .../member/info/service/MemberService.java | 137 +++++++- .../login/controller/AuthController.java | 36 -- .../login/controller/LoginController.java | 280 +--------------- .../login/dto/RefreshTokenRequestDTO.java | 12 + .../barogagi/member/login/dto/TokenPair.java | 3 +- .../InvalidRefreshTokenException.java | 10 +- .../login/exception/LoginException.java | 9 +- .../member/login/service/AuthService.java | 120 ++++--- .../member/login/service/LoginService.java | 243 +++++++++++++- .../com/barogagi/response/ApiResponse.java | 15 + .../terms/controller/TermsController.java | 136 +------- .../terms/exception/TermsException.java | 12 + .../barogagi/terms/service/TermsService.java | 127 ++++++- .../com/barogagi/util/MembershipUtil.java | 23 +- .../java/com/barogagi/util/Validator.java | 14 + .../util/exception/BasicException.java | 7 +- .../exception/GlobalExceptionHandler.java | 6 + src/main/resources/mapper/JoinMapper.xml | 4 +- 40 files changed, 1582 insertions(+), 1393 deletions(-) create mode 100644 src/main/java/com/barogagi/approval/exception/ApprovalException.java create mode 100644 src/main/java/com/barogagi/config/exception/BusinessException.java create mode 100644 src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java create mode 100644 src/main/java/com/barogagi/config/resultCode/ResultCode.java create mode 100644 src/main/java/com/barogagi/logging/ControllerLoggingAspect.java create mode 100644 src/main/java/com/barogagi/logging/ServiceLoggingAspect.java create mode 100644 src/main/java/com/barogagi/member/basic/join/exception/JoinException.java create mode 100644 src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java create mode 100644 src/main/java/com/barogagi/member/login/dto/RefreshTokenRequestDTO.java create mode 100644 src/main/java/com/barogagi/terms/exception/TermsException.java diff --git a/src/main/java/com/barogagi/approval/controller/ApprovalController.java b/src/main/java/com/barogagi/approval/controller/ApprovalController.java index d440b4e..602e481 100644 --- a/src/main/java/com/barogagi/approval/controller/ApprovalController.java +++ b/src/main/java/com/barogagi/approval/controller/ApprovalController.java @@ -1,54 +1,29 @@ package com.barogagi.approval.controller; import com.barogagi.approval.service.ApprovalService; -import com.barogagi.approval.service.AuthCodeService; import com.barogagi.approval.vo.ApprovalCompleteVO; import com.barogagi.approval.vo.ApprovalSendVO; -import com.barogagi.approval.vo.ApprovalVO; import com.barogagi.response.ApiResponse; -import com.barogagi.sendSms.dto.SendSmsVO; -import com.barogagi.sendSms.service.SendSmsService; -import com.barogagi.util.EncryptUtil; -import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; @Tag(name = "인증", description = "인증 API") @RestController @RequestMapping("/api/v1/verification-codes") public class ApprovalController { - private static final Logger logger = LoggerFactory.getLogger(ApprovalController.class); - @Autowired - private InputValidate inputValidate; - - @Autowired - private EncryptUtil encryptUtil; - - @Autowired - private AuthCodeService authCodeService; - - @Autowired - private ApprovalService approvalService; - - @Autowired - private SendSmsService sendSmsService; - - private final String API_SECRET_KEY; + private final ApprovalService approvalService; @Autowired - public ApprovalController(Environment environment){ - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + public ApprovalController(ApprovalService approvalService){ + this.approvalService = approvalService; } @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.
회원가입 시 사용할 경우 type 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "인증번호를 발송할 전화번호를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증번호 발송에 성공하었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "오류가 발생하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "인증번호 발송에 실패하였습니다."), @@ -57,86 +32,7 @@ public ApprovalController(Environment environment){ }) @PostMapping("/send") public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { - - logger.info("CALL /api/v1/verification-codes/send"); - logger.info("[input] API_SECRET_KEY={}", approvalSendVO.getApiSecretKey()); - - ApprovalVO approvalVO = new ApprovalVO(); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(approvalSendVO.getApiSecretKey().equals(API_SECRET_KEY)) { - - if(inputValidate.isEmpty(approvalSendVO.getTel())){ - resultCode = "101"; - message = "인증번호를 발송할 전화번호를 입력해주세요."; - - } else{ - - // 전화번호 - String recipientTel = approvalSendVO.getTel(); - - // 인증번호를 DB에 INSERT 전에, 전에 발송된 기록들은 flag UPDATE 처리 - approvalVO.setCompleteYn("N"); - approvalVO.setType(approvalSendVO.getType()); - - // 전화번호 암호화 - approvalVO.setTel(encryptUtil.hashEncodeString(approvalSendVO.getTel())); - - int updateResult = approvalService.updateApprovalRecord(approvalVO); - logger.info("@@ updateResult={}", updateResult); - - // 인증번호 생성 - String authCode = authCodeService.generateAuthCode(); - logger.info("@@ authCode={}", authCode); - - // 인증번호 메시지 발송 - SendSmsVO sendSmsVO = new SendSmsVO(); - sendSmsVO.setRecipientTel(recipientTel); - String messageContent = "인증번호는 [" + authCode + "] 입니다."; - sendSmsVO.setMessageContent(messageContent); - boolean sendMessageResult = sendSmsService.sendSms(sendSmsVO); - logger.info("@@ sendMessageResult={}", sendMessageResult); - - // 인증번호 암호화 - approvalVO.setAuthCode(encryptUtil.hashEncodeString(authCode)); - - // 인증번호를 DB에 insert - if(sendMessageResult){ - approvalVO.setMessageContent(sendSmsVO.getMessageContent()); - int insertResult = approvalService.insertApprovalRecord(approvalVO); - logger.info("@@ insertResult={}", insertResult); - if(insertResult > 0) { - // 인증번호 발송 로직 - resultCode = "200"; - message = "인증번호 발송에 성공하었습니다."; - - } else{ - resultCode = "102"; - message = "오류가 발생하였습니다."; - } - } else { - resultCode = "103"; - message = "인증번호 발송에 실패하였습니다."; - } - } - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return approvalService.approvalTelSend(approvalSendVO); } @Operation(summary = "인증번호 일치 여부 확인", description = "휴대전화번호에 발송된 인증번호와 입력된 인증번호가 동일한지 확인." + @@ -144,7 +40,7 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { "
authCode에는 인증번호를 넣어주세요." + "
tel에는 전화번호를 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호 또는 인증번호 값을 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증이 완료되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "인증이 실패하었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @@ -152,59 +48,6 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { }) @PostMapping("/verify") public ApiResponse approvalTelCheck(@RequestBody ApprovalCompleteVO approvalCompleteVO) { - - logger.info("CALL /api/v1/verification-codes/verify"); - logger.info("[input] API_SECRET_KEY={}", approvalCompleteVO.getApiSecretKey()); - - ApprovalVO approvalVO = new ApprovalVO(); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(approvalCompleteVO.getApiSecretKey().equals(API_SECRET_KEY)) { - if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode()) || inputValidate.isEmpty(approvalCompleteVO.getTel())){ - resultCode = "101"; - message = "전화번호 또는 인증번호 값을 입력해주세요."; - - } else{ - logger.info("@@@@ authCode = {}", approvalCompleteVO.getAuthCode()); - - // 전화번호 암호화 - approvalVO.setTel(encryptUtil.hashEncodeString(approvalCompleteVO.getTel())); - approvalVO.setCompleteYn("N"); - approvalVO.setAuthCode(encryptUtil.hashEncodeString(approvalCompleteVO.getAuthCode())); - approvalVO.setType(approvalCompleteVO.getType()); - - logger.info("authcode = {}", encryptUtil.hashEncodeString(approvalCompleteVO.getAuthCode())); - - int updateResult = approvalService.updateApprovalComplete(approvalVO); - logger.info("@@ updateResult={}", updateResult); - - if(updateResult == 1){ - resultCode = "200"; - message = "인증이 완료되었습니다."; - } else { - resultCode = "300"; - message = "인증에 실패하였습니다."; - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return approvalService.approvalTelCheck(approvalCompleteVO); } } diff --git a/src/main/java/com/barogagi/approval/exception/ApprovalException.java b/src/main/java/com/barogagi/approval/exception/ApprovalException.java new file mode 100644 index 0000000..5dde8e6 --- /dev/null +++ b/src/main/java/com/barogagi/approval/exception/ApprovalException.java @@ -0,0 +1,12 @@ +package com.barogagi.approval.exception; + +import com.barogagi.config.exception.BusinessException; +import lombok.Getter; + +@Getter +public class ApprovalException extends BusinessException { + + public ApprovalException(String resultCode, String message) { + super(resultCode, message); + } +} diff --git a/src/main/java/com/barogagi/approval/service/ApprovalService.java b/src/main/java/com/barogagi/approval/service/ApprovalService.java index 661a2ec..6cb6f64 100644 --- a/src/main/java/com/barogagi/approval/service/ApprovalService.java +++ b/src/main/java/com/barogagi/approval/service/ApprovalService.java @@ -1,18 +1,154 @@ package com.barogagi.approval.service; +import com.barogagi.approval.exception.ApprovalException; import com.barogagi.approval.mapper.ApprovalMapper; +import com.barogagi.approval.vo.ApprovalCompleteVO; +import com.barogagi.approval.vo.ApprovalSendVO; import com.barogagi.approval.vo.ApprovalVO; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.response.ApiResponse; +import com.barogagi.sendSms.dto.SendSmsVO; +import com.barogagi.sendSms.service.SendSmsService; +import com.barogagi.util.EncryptUtil; +import com.barogagi.util.InputValidate; +import com.barogagi.util.Validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestBody; @Service public class ApprovalService { private final ApprovalMapper approvalMapper; + private final Validator validator; + private final InputValidate inputValidate; + private final EncryptUtil encryptUtil; + private final AuthCodeService authCodeService; + private final SendSmsService sendSmsService; @Autowired - public ApprovalService(ApprovalMapper approvalMapper){ + public ApprovalService( + ApprovalMapper approvalMapper, + Validator validator, + InputValidate inputValidate, + EncryptUtil encryptUtil, + AuthCodeService authCodeService, + SendSmsService sendSmsService + ) + { this.approvalMapper = approvalMapper; + this.validator = validator; + this.inputValidate = inputValidate; + this.encryptUtil = encryptUtil; + this.authCodeService = authCodeService; + this.sendSmsService = sendSmsService; + } + + public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(approvalSendVO.getApiSecretKey())) { + throw new ApprovalException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(approvalSendVO.getTel())) { + throw new ApprovalException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 처리 + // 인증번호를 DB에 INSERT 전에, 전에 발송된 기록들은 flag UPDATE 처리 + ApprovalVO approvalVO = new ApprovalVO(); + approvalVO.setCompleteYn("N"); + approvalVO.setType(approvalSendVO.getType()); + + // 전화번호 암호화 + approvalVO.setTel(encryptUtil.hashEncodeString(approvalSendVO.getTel())); + + int updateResult = this.updateApprovalRecord(approvalVO); + + // 인증번호 생성 + String authCode = authCodeService.generateAuthCode(); + + // 인증번호 메시지 발송 + SendSmsVO sendSmsVO = new SendSmsVO(); + sendSmsVO.setRecipientTel(approvalSendVO.getTel()); + String messageContent = "인증번호는 [" + authCode + "] 입니다."; + sendSmsVO.setMessageContent(messageContent); + boolean sendMessageResult = sendSmsService.sendSms(sendSmsVO); + + if(!sendMessageResult) { + throw new ApprovalException( + ProcessResultCode.FAIL_SEND_SMS.getResultCode(), + ProcessResultCode.FAIL_SEND_SMS.getMessage() + ); + } + + // 인증번호 암호화 + approvalVO.setAuthCode(encryptUtil.hashEncodeString(authCode)); + + approvalVO.setMessageContent(sendSmsVO.getMessageContent()); + int insertResult = this.insertApprovalRecord(approvalVO); + + if(insertResult <= 0) { + throw new ApprovalException( + ProcessResultCode.ERROR_SEND_SMS.getResultCode(), + ProcessResultCode.ERROR_SEND_SMS.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_SEND_SMS.getResultCode(), + ProcessResultCode.SUCCESS_SEND_SMS.getMessage() + ); + } + + public ApiResponse approvalTelCheck(ApprovalCompleteVO approvalCompleteVO) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(approvalCompleteVO.getApiSecretKey())) { + throw new ApprovalException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode()) + || inputValidate.isEmpty(approvalCompleteVO.getTel())) { + throw new ApprovalException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 전화번호 암호화 + ApprovalVO approvalVO = new ApprovalVO(); + approvalVO.setTel(encryptUtil.hashEncodeString(approvalCompleteVO.getTel())); + approvalVO.setCompleteYn("N"); + approvalVO.setAuthCode(encryptUtil.hashEncodeString(approvalCompleteVO.getAuthCode())); + approvalVO.setType(approvalCompleteVO.getType()); + + // 4. 인증 + int updateResult = this.updateApprovalComplete(approvalVO); + if(updateResult != 1){ + throw new ApprovalException( + ProcessResultCode.FAIL_CHECK_SMS.getResultCode(), + ProcessResultCode.FAIL_CHECK_SMS.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_CHECK_SMS.getResultCode(), + ProcessResultCode.SUCCESS_CHECK_SMS.getMessage() + ); } public int updateApprovalRecord(ApprovalVO vo){ diff --git a/src/main/java/com/barogagi/config/JwtAuthFilter.java b/src/main/java/com/barogagi/config/JwtAuthFilter.java index e1b1fec..40c4f70 100644 --- a/src/main/java/com/barogagi/config/JwtAuthFilter.java +++ b/src/main/java/com/barogagi/config/JwtAuthFilter.java @@ -2,33 +2,29 @@ import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.service.MemberService; -import com.barogagi.member.login.repository.UserMembershipRepository; +import com.barogagi.member.login.exception.InvalidRefreshTokenException; import com.barogagi.util.JwtUtil; +import com.barogagi.config.resultCode.ResultCode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.*; import jakarta.servlet.http.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; + import java.io.IOException; @Component public class JwtAuthFilter extends OncePerRequestFilter { - Logger logger = LoggerFactory.getLogger(JwtAuthFilter.class); - private final JwtUtil jwt; - private final UserMembershipRepository userRepo; private final MemberService memberService; - public JwtAuthFilter(JwtUtil jwt, UserMembershipRepository userRepo, MemberService memberService) { + public JwtAuthFilter(JwtUtil jwt, MemberService memberService) { this.jwt = jwt; - this.userRepo = userRepo; this.memberService = memberService; } @@ -38,16 +34,11 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, try { String header = req.getHeader("Authorization"); - - logger.info("@@@ header={}", header); - if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); - logger.info("@@@ token={}", token); Claims claims = jwt.parseToken(token, "ACCESS"); String membershipNo = jwt.getMembershipNo(claims); - logger.info("@@@ membershipNo={}", membershipNo); // 회원 조회 Member member = memberService.findByMembershipNo(membershipNo); @@ -62,12 +53,21 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, chain.doFilter(req, res); } catch (ExpiredJwtException e) { // 유효기간이 지나서 만료된 경우 - writeErrorResponse(res, "300", "Access token has expired"); + writeErrorResponse( + ResultCode.EXPIRE_TOKEN.getResultCode(), + ResultCode.EXPIRE_TOKEN.getMessage() + ); } catch (JwtException | SecurityException e) { // 위조되었거나 변조되었거나 구조가 잘못되었을 경우 - writeErrorResponse(res, "301", "Revoked access token"); + writeErrorResponse( + ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), + ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() + ); } catch (Exception e) { - writeErrorResponse(res, "302", "Unknown authentication error"); + writeErrorResponse( + ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), + ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() + ); } } @@ -78,16 +78,8 @@ protected boolean shouldNotFilter(HttpServletRequest request) { return p.startsWith("/auth/") || p.startsWith("/login/basic/membership/userId/search"); } - private void writeErrorResponse(HttpServletResponse res, String errorCode, String message) throws IOException { - res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - res.setContentType("application/json;charset=UTF-8"); - - String json = String.format( - "{\"errorCode\":\"%s\", \"message\":\"%s\"}", - errorCode, message - ); - - res.getWriter().write(json); + private void writeErrorResponse(String resultCode, String message) throws IOException { + throw new InvalidRefreshTokenException(resultCode, message); } } diff --git a/src/main/java/com/barogagi/config/OAuth2LoginSuccessHandler.java b/src/main/java/com/barogagi/config/OAuth2LoginSuccessHandler.java index 12a90f3..f2a30c2 100644 --- a/src/main/java/com/barogagi/config/OAuth2LoginSuccessHandler.java +++ b/src/main/java/com/barogagi/config/OAuth2LoginSuccessHandler.java @@ -42,6 +42,8 @@ public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res.setContentType("application/json;charset=UTF-8"); objectMapper.writeValue(res.getWriter(), Map.of( + "resultCode", login.tokens().resultCode(), + "message", login.tokens().message(), "accessToken", login.tokens().accessToken(), "accessTokenExpiresIn", login.tokens().accessTokenExpiresIn(), "userId", userId, diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index b07721a..b8952a7 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -2,6 +2,7 @@ import com.barogagi.member.oauth.join.service.CustomOidcUserService; import com.barogagi.member.oauth.join.service.DelegatingOAuth2UserService; +import com.barogagi.config.resultCode.ResultCode; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +44,7 @@ public SecurityConfig(JwtAuthFilter jwtAuthFilter, "/api/v1/terms/**", // 약관 관련 "/api/v1/home/tags/popular", // 인기 태그 조회 "/api/v1/home/regions/popular", // 인기 지역 조회 + "/api/v1/verification-codes/**" }; @Bean @@ -74,9 +76,17 @@ SecurityFilterChain filterChain( .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class) // 브라우저 리다이렉트 대신 401 JSON .exceptionHandling(ex -> ex.authenticationEntryPoint((req, res, e) -> { + + String resultCode = ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(); + String message = ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage(); + res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.setContentType("application/json;charset=UTF-8"); - res.getWriter().write("{\"error\":\"unauthorized\"}"); + String json = String.format( + "{\"resultCode\":\"%s\", \"message\":\"%s\"}", + resultCode, message + ); + res.getWriter().write(json); })); return http.build(); diff --git a/src/main/java/com/barogagi/config/exception/BusinessException.java b/src/main/java/com/barogagi/config/exception/BusinessException.java new file mode 100644 index 0000000..a87dcdf --- /dev/null +++ b/src/main/java/com/barogagi/config/exception/BusinessException.java @@ -0,0 +1,14 @@ +package com.barogagi.config.exception; + +import lombok.Getter; + +@Getter +public abstract class BusinessException extends RuntimeException { + + private final String resultCode; + + public BusinessException(String resultCode, String message) { + super(message); + this.resultCode = resultCode; + } +} diff --git a/src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java b/src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java new file mode 100644 index 0000000..a2d9f18 --- /dev/null +++ b/src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java @@ -0,0 +1,82 @@ +package com.barogagi.config.resultCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ProcessResultCode { + + EMPTY_DATA("101", "정보를 입력해주세요."), + + // nickname + INVALID_NICKNAME("102", "적합하지 않는 닉네임입니다."), + UNAVAILABLE_NICKNAME("103", "해당 닉네임 사용이 불가능합니다."), + AVAILABLE_NICKNAME("200", "사용 가능한 닉네임입니다."), + + // userId + INVALID_USER_ID("102", "적합한 아이디가 아닙니다."), + UNAVAILABLE_USER_ID("300", "해당 아이디 사용이 불가능합니다."), + AVAILABLE_USER_ID("200", "해당 아이디 사용이 가능합니다."), + + // signUp + INVALID_SIGN_UP("102", "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), + SUCCESS_SIGN_UP("200", "회원가입에 성공하였습니다."), + FAIL_SIGN_UP("300", "회원가입에 실패하였습니다."), + + // deleteAccount + SUCCESS_DELETE_ACCOUNT("200", "회원 탈퇴되었습니다."), + FAIL_DELETE_ACCOUNT("300", "회원 탈퇴 실패하였습니다."), + + // findUser + FOUND_ACCOUNT("200", "해당 전화번호로 가입된 아이디가 존재합니다."), + NOT_FOUND_ACCOUNT("201", "해당 전화번호로 가입된 계정이 존재하지 않습니다."), + + // updatePassword + SUCCESS_UPDATE_PASSWORD("200", "비밀번호 재설정에 성공하였습니다."), + FAIL_UPDATE_PASSWORD("300", "비밀번호 재설정에 실패하였습니다."), + + // Login + NOT_FOUND_USER_INFO("102", "회원 정보가 존재하지 않습니다."), + FAIL_LOGIN("103", "로그인에 실패하였습니다."), + SUCCESS_LOGIN("200", "로그인에 성공하였습니다."), + + // refreshToken + REQUIRED_LOGIN("110", "로그인을 진행해주세요."), + REQUIRED_RE_LOGIN("120", "로그인을 다시 진행해주세요."), + SUCCESS_REFRESH_TOKEN("200", "토큰이 발급되었습니다."), + FAIL_REFRESH_TOKEN("130", "토큰 발급에 실패하였습니다."), + + // logout + FAIL_LOGOUT("300", "로그아웃 실패하였습니다."), + SUCCESS_LOGOUT("200", "로그아웃 되었습니다."), + + // terms + FOUND_TERMS("200", "약관 조회에 성공하였습니다."), + NOT_FOUND_TERMS("102", "약관이 존재하지 않습니다."), + SUCCESS_INSERT_TERMS("200", "약관 저장에 성공하였습니다."), + FAIL_INSERT_TERMS("300", "약관 저장에 실패하였습니다."), + + // memberInfo + FOUND_USER_INFO("200", "회원 정보 조회가 완료되었습니다."), + FAIL_UPDATE_USER_INFO("404", "사용자 정보 수정 실패하였습니다."), + SUCCESS_UPDATE_USER_INFO("200", "사용자 정보 수정 완료하였습니다."), + + // mainPage + NOT_FOUND_SCHEDULE("201", "일정이 존재하지 않습니다."), + FOUND_SCHEDULE("200", "조회 성공하였습니다."), + NOT_FOUND_POPULAR_TAG("201", "인기 태그 목록이 존재하지 않습니다."), + FOUND_POPULAR_TAG("200", "인기 태그 조회 완료하였습니다."), + NOT_FOUND_POPULAR_REGION("201", "인기 지역 목록이 존재하지 않습니다."), + FOUND_POPULAR_REGION("200", "인기 지역 조회 완료하였습니다."), + + // approval + SUCCESS_SEND_SMS("200", "인증번호 발송에 성공하었습니다."), + FAIL_SEND_SMS("103", "인증번호 발송에 실패하였습니다."), + ERROR_SEND_SMS("102", "오류가 발생하였습니다."), + SUCCESS_CHECK_SMS("200", "인증이 완료되었습니다."), + FAIL_CHECK_SMS("300", "인증에 실패하였습니다."); + + private final String resultCode; + private final String message; +} diff --git a/src/main/java/com/barogagi/config/resultCode/ResultCode.java b/src/main/java/com/barogagi/config/resultCode/ResultCode.java new file mode 100644 index 0000000..a854ce5 --- /dev/null +++ b/src/main/java/com/barogagi/config/resultCode/ResultCode.java @@ -0,0 +1,23 @@ +package com.barogagi.config.resultCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ResultCode { + + // API_SECRET_KEY 일치 X + NOT_EQUAL_API_SECRET_KEY("100", "잘못된 접근입니다."), + + // ACCESS TOKEN + NOT_EXIST_ACCESS_AUTH("401", "접근 권한이 존재하지 않습니다."), + EXIST_ACCESS_AUTH("200", "회원 번호가 존재합니다."), + EXPIRE_TOKEN("300", "Token이 만료되었습니다."), + + // 서버 오류 + ERROR("400","오류가 발생하였습니다."); + + private final String resultCode; + private final String message; +} diff --git a/src/main/java/com/barogagi/logging/ControllerLoggingAspect.java b/src/main/java/com/barogagi/logging/ControllerLoggingAspect.java new file mode 100644 index 0000000..240551b --- /dev/null +++ b/src/main/java/com/barogagi/logging/ControllerLoggingAspect.java @@ -0,0 +1,27 @@ +package com.barogagi.logging; + +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Slf4j +@Aspect +@Component +public class ControllerLoggingAspect { + + @Around("@within(org.springframework.web.bind.annotation.RestController)") + public Object logController(ProceedingJoinPoint joinPoint) throws Throwable { + + String className = joinPoint.getTarget().getClass().getSimpleName(); + String methodName = joinPoint.getSignature().getName(); + + log.info("Controller Start - {}.{}", className, methodName); + + Object result = joinPoint.proceed(); + log.info("Controller End - {}.{}", className, methodName); + + return result; + } +} diff --git a/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java b/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java new file mode 100644 index 0000000..0e43bf7 --- /dev/null +++ b/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java @@ -0,0 +1,44 @@ +package com.barogagi.logging; + +import com.barogagi.config.exception.BusinessException; +import com.barogagi.response.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +@Slf4j +@Aspect +@Component +public class ServiceLoggingAspect { + + @Around("execution(* com.barogagi..service..*(..))") + public Object logService(ProceedingJoinPoint joinPoint) throws Throwable { + + String className = joinPoint.getTarget().getClass().getSimpleName(); + String methodName = joinPoint.getSignature().getName(); + + long startTime = System.currentTimeMillis(); + + log.info("Service Start - {}.{}", className, methodName); + + try { + Object result = joinPoint.proceed(); + + long elapsedTime = System.currentTimeMillis() - startTime; + log.info("Service End - {}.{}, time={}ms", + className, methodName, elapsedTime); + + return result; + } catch (BusinessException ex) { + log.error("Service BusinessException - {}.{} / resultCode - {} / message - {}", className, methodName, ex.getResultCode(), ex.getMessage(), ex); + throw ex; + + } catch (Exception e) { + log.error("Service Exception - {}.{}", className, methodName, e); + throw e; + } + } +} + diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index c7919f1..a7db4fb 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -1,43 +1,24 @@ package com.barogagi.mainPage.controller; -import com.barogagi.mainPage.dto.*; -import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.mainPage.response.MainPageResponse; import com.barogagi.mainPage.service.MainPageService; import com.barogagi.response.ApiResponse; -import com.barogagi.util.MembershipUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.Map; - @Tag(name = "메인 화면", description = "메인 화면에 필요한 API") @RestController @RequestMapping("/api/v1/home") public class MainPageController { - private static final Logger logger = LoggerFactory.getLogger(MainPageController.class); - private final MainPageService mainPageService; - private final MembershipUtil membershipUtil; - - private final String API_SECRET_KEY; - @Autowired - public MainPageController(MainPageService mainPageService, - MembershipUtil membershipUtil, - Environment environment) { + public MainPageController(MainPageService mainPageService) { this.mainPageService = mainPageService; - this.membershipUtil = membershipUtil; - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); } @Operation(summary = "유저 일정 정보 API", description = "메인 화면 - 다가오는 일정 부분에 해당하는 API", @@ -49,75 +30,7 @@ public MainPageController(MainPageService mainPageService, }) @GetMapping("/me/schedules") public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { - - logger.info("CALL /api/v1/home/me/schedules"); - - MainPageResponse mainPageResponse = new MainPageResponse(); - String resultCode = ""; - String message = ""; - - try { - - // 회원번호 구하기 - Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MainPageException(String.valueOf(membershipNoInfo.get("resultCode")), - String.valueOf(membershipNoInfo.get("message"))); - } - - String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); - - // 유저 일정 정보 API - UserInfoRequestDTO userInfoRequestDTO = new UserInfoRequestDTO(); - userInfoRequestDTO.setMembershipNo(membershipNo); - - UserInfoResponseDTO userInfoResponseDTO = mainPageService.selectUserScheduleInfo(userInfoRequestDTO); - - if(null == userInfoResponseDTO) { - resultCode = "201"; - message = "일정이 존재하지 않습니다."; - - } else { - - resultCode = "200"; - message = "조회 성공하였습니다."; - - mainPageResponse.setUserInfoResponseDTO(userInfoResponseDTO); - - // 일정 번호 - userInfoRequestDTO.setScheduleNum(userInfoResponseDTO.getScheduleNum()); - - // 해당 schedule에 대한 태그 목록 조회 - List tagList = mainPageService.selectScheduleTag(userInfoRequestDTO); - - if(!tagList.isEmpty()) { - mainPageResponse.setTagInfoList(tagList); - } - - // 해당 plan에 대한 region 정보 조회 - userInfoRequestDTO.setPlanNum(userInfoResponseDTO.getPlanNum()); - - RegionInfoDTO regionInfo = mainPageService.selectScheduleRegionInfo(userInfoRequestDTO); - - if(null != regionInfo) { - mainPageResponse.setRegionInfoDTO(regionInfo); - } - } - - } catch (MainPageException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - mainPageResponse.setResultCode(resultCode); - mainPageResponse.setMessage(message); - } - - return mainPageResponse; + return mainPageService.selectUserScheduleInfoProcess(request); } @Operation(summary = "인기 태그 조회 API ", description = "메인 화면 - 오늘 많이 생성되는 일정 부분에 해당하는 API", @@ -129,46 +42,7 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { }) @GetMapping("/tags/popular") public ApiResponse selectPopularTagList(@RequestHeader("API-KEY") String apiSecretKey) { - - logger.info("CALL /api/v1/home/tags/popular"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(!apiSecretKey.equals(API_SECRET_KEY)) { - throw new MainPageException("100", "잘못된 접근입니다."); - } - - // 인기 태그 조회 - List tagRankInfoList = mainPageService.selectTagRankList(); - - logger.info("@@ !tagRankInfoList.isEmpty()={}", !tagRankInfoList.isEmpty()); - if(!tagRankInfoList.isEmpty()) { - resultCode = "200"; - message = "인기 태그 조회 완료하였습니다."; - apiResponse.setData(tagRankInfoList); - } else { - resultCode = "201"; - message = "인기 태그 목록이 존재하지 않습니다."; - } - - } catch (MainPageException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return mainPageService.selectPopularTagList(apiSecretKey); } @Operation(summary = "인기 지역 조회 API ", description = "메인 화면 - 지금 인기많은 핫 플레이스 부분에 해당하는 API", @@ -180,44 +54,6 @@ public ApiResponse selectPopularTagList(@RequestHeader("API-KEY") String apiSecr }) @GetMapping("/regions/popular") public ApiResponse selectPopularRegionList(@RequestHeader("API-KEY") String apiSecretKey) { - logger.info("CALL /api/v1/home/regions/popular"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(!apiSecretKey.equals(API_SECRET_KEY)) { - throw new MainPageException("100", "잘못된 접근입니다."); - } - - // 인기 지역 조회 - List regionRankInfoList = mainPageService.selectRegionRankList(); - - logger.info("@@ !regionRankInfoList.isEmpty()={}", !regionRankInfoList.isEmpty()); - if(!regionRankInfoList.isEmpty()) { - resultCode = "200"; - message = "인기 지역 조회 완료하였습니다."; - apiResponse.setData(regionRankInfoList); - } else { - resultCode = "201"; - message = "인기 지역 목록이 존재하지 않습니다."; - } - - } catch (MainPageException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return mainPageService.selectPopularRegionList(apiSecretKey); } } diff --git a/src/main/java/com/barogagi/mainPage/exception/MainPageException.java b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java index d42c740..11e04cf 100644 --- a/src/main/java/com/barogagi/mainPage/exception/MainPageException.java +++ b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java @@ -1,14 +1,12 @@ package com.barogagi.mainPage.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class MainPageException extends RuntimeException { - - private final String resultCode; +public class MainPageException extends BusinessException { public MainPageException(String resultCode, String message) { - super(message); - this.resultCode = resultCode; + super(resultCode, message); } } diff --git a/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java b/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java index 22b484f..972c4d4 100644 --- a/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java +++ b/src/main/java/com/barogagi/mainPage/response/MainPageResponse.java @@ -21,4 +21,24 @@ public class MainPageResponse { private UserInfoResponseDTO userInfoResponseDTO; private List tagInfoList; private RegionInfoDTO regionInfoDTO; + + public static MainPageResponse resultData( + UserInfoResponseDTO userInfoResponseDTO, + List tagInfoList, + RegionInfoDTO regionInfoDTO, + String resultCode, + String message + ) + { + MainPageResponse mainPageResponse = new MainPageResponse(); + + mainPageResponse.userInfoResponseDTO = userInfoResponseDTO; + mainPageResponse.tagInfoList = tagInfoList; + mainPageResponse.regionInfoDTO = regionInfoDTO; + + mainPageResponse.resultCode = resultCode; + mainPageResponse.message = message; + + return mainPageResponse; + } } diff --git a/src/main/java/com/barogagi/mainPage/service/MainPageService.java b/src/main/java/com/barogagi/mainPage/service/MainPageService.java index ff6e9f4..3f766c4 100644 --- a/src/main/java/com/barogagi/mainPage/service/MainPageService.java +++ b/src/main/java/com/barogagi/mainPage/service/MainPageService.java @@ -1,13 +1,22 @@ package com.barogagi.mainPage.service; import com.barogagi.mainPage.dto.*; +import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.mainPage.mapper.MainPageMapper; +import com.barogagi.mainPage.response.MainPageResponse; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.response.ApiResponse; +import com.barogagi.util.MembershipUtil; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.Validator; +import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; +import java.util.Map; @Service public class MainPageService { @@ -15,34 +24,140 @@ public class MainPageService { private static final Logger logger = LoggerFactory.getLogger(MainPageService.class); private final MainPageMapper mainPageMapper; + private final MembershipUtil membershipUtil; + private final Validator validator; @Autowired - public MainPageService(MainPageMapper mainPageMapper) { + public MainPageService( + MainPageMapper mainPageMapper, + MembershipUtil membershipUtil, + Validator validator + ) + { this.mainPageMapper = mainPageMapper; + this.membershipUtil = membershipUtil; + this.validator = validator; + } + + public MainPageResponse selectUserScheduleInfoProcess(HttpServletRequest request) { + + String resultCode = ""; + String message = ""; + List tagList = null; + RegionInfoDTO regionInfo = null; + + // 1. 회원번호 구하기 + Map membershipNoInfo = membershipUtil.membershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MainPageException( + String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message")) + ); + } + + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); + + // 2. 유저 일정 정보 조회 + UserInfoRequestDTO userInfoRequestDTO = new UserInfoRequestDTO(); + userInfoRequestDTO.setMembershipNo(membershipNo); + UserInfoResponseDTO userInfoResponseDTO = this.selectUserScheduleInfo(userInfoRequestDTO); + + if(null == userInfoResponseDTO) { + resultCode = ProcessResultCode.NOT_FOUND_SCHEDULE.getResultCode(); + message = ProcessResultCode.NOT_FOUND_SCHEDULE.getMessage(); + } else { + + resultCode = ProcessResultCode.FOUND_SCHEDULE.getResultCode(); + message = ProcessResultCode.FOUND_SCHEDULE.getMessage(); + + // 3. 해당 schedule에 대한 태그 목록 조회 + userInfoRequestDTO.setScheduleNum(userInfoResponseDTO.getScheduleNum()); + tagList = this.selectScheduleTag(userInfoRequestDTO); + + // 4. 해당 plan에 대한 region 정보 조회 + userInfoRequestDTO.setPlanNum(userInfoResponseDTO.getPlanNum()); + regionInfo = this.selectScheduleRegionInfo(userInfoRequestDTO); + } + + return MainPageResponse.resultData(userInfoResponseDTO, tagList, regionInfo, resultCode, message); + } + + public ApiResponse selectPopularTagList(String apiSecretKey) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new MainPageException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 인기 태그 조회 + List tagRankInfoList = this.selectTagRankList(); + if(tagRankInfoList.isEmpty()) { + throw new MainPageException( + ProcessResultCode.NOT_FOUND_POPULAR_TAG.getResultCode(), + ProcessResultCode.NOT_FOUND_POPULAR_TAG.getMessage() + ); + + } + + return ApiResponse.resultData( + tagRankInfoList, + ProcessResultCode.FOUND_POPULAR_TAG.getResultCode(), + ProcessResultCode.FOUND_POPULAR_TAG.getMessage() + ); + } + + public ApiResponse selectPopularRegionList(String apiSecretKey) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new MainPageException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 인기 지역 조회 + List regionRankInfoList = this.selectRegionRankList(); + + if(regionRankInfoList.isEmpty()) { + throw new MainPageException( + ProcessResultCode.NOT_FOUND_POPULAR_REGION.getResultCode(), + ProcessResultCode.NOT_FOUND_POPULAR_REGION.getMessage() + ); + } + + return ApiResponse.resultData( + regionRankInfoList, + ProcessResultCode.FOUND_POPULAR_REGION.getResultCode(), + ProcessResultCode.FOUND_POPULAR_REGION.getMessage() + ); } // 유저 일정 조회 - public UserInfoResponseDTO selectUserScheduleInfo(UserInfoRequestDTO userInfoRequestDTO) throws Exception { + public UserInfoResponseDTO selectUserScheduleInfo(UserInfoRequestDTO userInfoRequestDTO) { return mainPageMapper.selectUserScheduleInfo(userInfoRequestDTO); } // 해당 schedule에 대한 태그 목록 조회 - public List selectScheduleTag(UserInfoRequestDTO userInfoRequestDTO) throws Exception { + public List selectScheduleTag(UserInfoRequestDTO userInfoRequestDTO) { return mainPageMapper.selectScheduleTag(userInfoRequestDTO); } // 해당 plan에 대한 region 정보 조회 - public RegionInfoDTO selectScheduleRegionInfo(UserInfoRequestDTO userInfoRequestDTO) throws Exception { + public RegionInfoDTO selectScheduleRegionInfo(UserInfoRequestDTO userInfoRequestDTO) { return mainPageMapper.selectScheduleRegionInfo(userInfoRequestDTO); } // 인기 지역 조회 - public List selectRegionRankList() throws Exception { + public List selectRegionRankList() { return mainPageMapper.selectRegionRankList(); } // 인기 태그 조회 - public List selectTagRankList() throws Exception { + public List selectTagRankList() { return mainPageMapper.selectTagRankList(); } } diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index 3338461..7641ee8 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -1,357 +1,78 @@ package com.barogagi.member.basic.join.controller; -import com.barogagi.config.PasswordConfig; -import com.barogagi.member.basic.join.dto.NickNameDTO; -import com.barogagi.member.basic.join.service.JoinService; -import com.barogagi.member.basic.join.dto.JoinDTO; +import com.barogagi.member.basic.join.service.BasicJoinService; import com.barogagi.member.basic.join.dto.JoinRequestDTO; -import com.barogagi.member.login.exception.InvalidRefreshTokenException; -import com.barogagi.member.login.service.AccountService; -import com.barogagi.member.login.service.AuthService; +import com.barogagi.member.login.dto.RefreshTokenRequestDTO; import com.barogagi.response.ApiResponse; -import com.barogagi.util.EncryptUtil; -import com.barogagi.util.InputValidate; -import com.barogagi.util.Validator; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.*; -import java.util.Map; -import java.util.Optional; - @Tag(name = "일반 회원가입", description = "일반 회원가입 관련 API") @RestController @RequestMapping("/api/v1/users") public class JoinController { - private static final Logger logger = LoggerFactory.getLogger(JoinController.class); - - private final JoinService joinService; - private final AccountService accountService; - private final AuthService authService; - private final InputValidate inputValidate; - private final EncryptUtil encryptUtil; - private final Validator validator; - private final PasswordConfig passwordConfig; - private final String API_SECRET_KEY; + private final BasicJoinService basicJoinService; @Autowired - public JoinController(Environment environment, - JoinService joinService, - AccountService accountService, - AuthService authService, - InputValidate inputValidate, - EncryptUtil encryptUtil, - Validator validator, - PasswordConfig passwordConfig) { - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); - this.joinService = joinService; - this.accountService = accountService; - this.authService = authService; - this.inputValidate = inputValidate; - this.encryptUtil = encryptUtil; - this.validator = validator; - this.passwordConfig = passwordConfig; + public JoinController(BasicJoinService basicJoinService) { + this.basicJoinService = basicJoinService; } @Operation(summary = "아이디 중복 체크 기능", description = "아이디 중복 체크 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 아이디 사용이 가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "아이디를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디가 아닙니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "해당 아이디 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/userid/exists") public ApiResponse checkUserId(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String userId) { - - logger.info("CALL /api/v1/users/userId/exists"); - logger.info("[input] API_SECRET_KEY={}", apiSecretKey); - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(apiSecretKey.equals(API_SECRET_KEY)){ - - if(inputValidate.isEmpty(userId)) { - resultCode = "101"; - message = "아이디를 입력해주세요."; - } else{ - - if(!validator.isValidId(userId)) { - resultCode = "102"; - message = "적합한 아이디가 아닙니다."; - } else { - JoinDTO joinDTO = new JoinDTO(); - joinDTO.setUserId(userId); - - int checkUserId = joinService.checkUserId(joinDTO); - logger.info("@@ checkUserId={}", checkUserId); - - if(checkUserId > 0){ - resultCode = "300"; - message = "해당 아이디 사용이 불가능합니다."; - - } else{ - resultCode = "200"; - message = "해당 아이디 사용이 가능합니다."; - } - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return basicJoinService.checkUserId(apiSecretKey, userId); } @Operation(summary = "회원가입 정보 저장 기능", description = "회원가입 정보 저장 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원가입에 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "회원가입에 필요한 정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 아이디에 대한 회원 정보가 이미 존재합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원가입에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping - public ApiResponse membershipJoinInsert(@RequestBody JoinRequestDTO joinRequestDTO){ - - logger.info("CALL /api/v1/users"); - logger.info("[input] API_SECRET_KEY={}", joinRequestDTO.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(joinRequestDTO.getApiSecretKey().equals(API_SECRET_KEY)){ - - // 필수 입력값(아이디, 비밀번호, 휴대전화번호 값이 빈 값이 아닌지 확인) - // 선택 입력값(이메일, 생년월일, 성별, 닉네임) - if(inputValidate.isEmpty(joinRequestDTO.getUserId()) || inputValidate.isEmpty(joinRequestDTO.getPassword()) || inputValidate.isEmpty(joinRequestDTO.getTel())){ - - // 필수 입력값 중 빈 값이 존재. insert 중지 - resultCode = "101"; - message = "회원가입에 필요한 정보를 입력해주세요."; - - } else{ - - // 아이디, 비밀번호, 닉네임 적합성 검사 - if(!(validator.isValidId(joinRequestDTO.getUserId()) && validator.isValidPassword(joinRequestDTO.getPassword()) && validator.isValidNickname(joinRequestDTO.getNickName()))){ - resultCode = "102"; - message = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."; - } else { - // 입력값 암호화 & 값 세팅 - // 휴대전화번호, 비밀번호 암호화 - joinRequestDTO.setTel(encryptUtil.encrypt(joinRequestDTO.getTel().replaceAll("[^0-9]", ""))); - - // 이메일 값이 넘어오면 암호화 - if(!inputValidate.isEmpty(joinRequestDTO.getEmail())){ - joinRequestDTO.setEmail(encryptUtil.encrypt(joinRequestDTO.getEmail())); - } - - String encodedPassword = passwordConfig.passwordEncoder().encode(joinRequestDTO.getPassword()); - joinRequestDTO.setPassword(encodedPassword); - - // 회원 정보 저장(회원가입) - JoinDTO joinDTO = new JoinDTO(); - joinDTO.setUserId(joinRequestDTO.getUserId()); - joinDTO.setPassword(joinRequestDTO.getPassword()); - joinDTO.setEmail(joinRequestDTO.getEmail()); - joinDTO.setBirth(joinRequestDTO.getBirth().replaceAll("[^0-9]", "")); - joinDTO.setTel(joinRequestDTO.getTel()); - joinDTO.setGender(joinRequestDTO.getGender()); - joinDTO.setNickName(joinRequestDTO.getNickName()); - joinDTO.setJoinType("BASIC"); - - // 아이디 중복 검증 - int duplicateUserId = joinService.checkUserId(joinDTO); - - if(duplicateUserId > 0) { - resultCode = "103"; - message = "해당 아이디에 대한 회원 정보가 이미 존재합니다."; - } else { - int insertResult = joinService.insertMembershipInfo(joinDTO); - logger.info("@@ insertResult={}", insertResult); - - if(insertResult > 0){ - resultCode = "200"; - message = "회원가입에 성공하였습니다."; - } else{ - resultCode = "300"; - message = "회원가입에 실패하였습니다."; - } - } - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + public ApiResponse signUp(@RequestBody JoinRequestDTO joinRequestDTO) { + return basicJoinService.signUp(joinRequestDTO); } - @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API입니다.", + @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "이용 가능한 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용 가능한 닉네임입니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "닉네임 데이터를 보내주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합하지 않는 닉네임입니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "이미 존재하는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 닉네임 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/nickname/exists") - public ApiResponse checkDuplicateNickname(@RequestHeader("API-KEY") String apiSecretKey, - @RequestParam String nickname){ - - logger.info("CALL /api/v1/user/nickname/exists"); - logger.info("[input] API_SECRET_KEY={}", apiSecretKey); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - if(apiSecretKey.equals(API_SECRET_KEY)){ - - // 필수 입력값 - if(inputValidate.isEmpty(nickname)){ - - // 필수 입력값 중 빈 값이 존재. insert 중지 - resultCode = "101"; - message = "닉네임 정보를 입력해주세요."; - - } else{ - - if(!validator.isValidNickname(nickname)) { - resultCode = "102"; - message = "적합하지 않는 닉네임입니다."; - } else { - - NickNameDTO nickNameDTO = new NickNameDTO(); - nickNameDTO.setNickName(nickname); - - int nickNameCnt = joinService.checkNickName(nickNameDTO); - logger.info("nickNameCnt={}", nickNameCnt); - if(nickNameCnt > 0) { - resultCode = "103"; - message = "이미 존재하는 닉네임입니다."; - } else { - resultCode = "200"; - message = "이용 가능한 닉네임입니다."; - } - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + public ApiResponse checkDuplicateNickname(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String nickname){ + return basicJoinService.checkNickname(apiSecretKey, nickname); } @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "refresh token이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @DeleteMapping("/me") - public ApiResponse deleteMe(@RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body) { - - logger.info("CALL /api/v1/users/me"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - String refreshToken = Optional.ofNullable(refreshHeader) - .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) - .orElse(null); - - if (refreshToken == null || refreshToken.isBlank()) { - throw new InvalidRefreshTokenException("100", "refresh token이 존재하지 않습니다."); - } - - // refresh token을 이용해서 membershipNo 구하기 - - Map resultMap = authService.selectUserInfoByToken(refreshToken); - if(!resultMap.get("resultCode").equals("200")) { - throw new InvalidRefreshTokenException(resultMap.get("resultCode"), resultMap.get("message")); - } - - String membershipNo = resultMap.get("membershipNo"); - - int deleteResult = accountService.deleteMyAccount(membershipNo); - if(deleteResult > 0) { - resultCode = "200"; - message = "회원 탈퇴되었습니다."; - } else { - resultCode = "300"; - message = "회원 탈퇴 실패하였습니다."; - } - - } catch (InvalidRefreshTokenException ex) { - resultCode = ex.getCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + public ApiResponse deleteAccount(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { + return basicJoinService.deleteAccount(refreshTokenRequestDTO); } } diff --git a/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java b/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java new file mode 100644 index 0000000..6c1b42f --- /dev/null +++ b/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java @@ -0,0 +1,12 @@ +package com.barogagi.member.basic.join.exception; + +import com.barogagi.config.exception.BusinessException; +import lombok.Getter; + +@Getter +public class JoinException extends BusinessException { + + public JoinException(String resultCode, String message) { + super(resultCode, message); + } +} diff --git a/src/main/java/com/barogagi/member/basic/join/mapper/JoinMapper.java b/src/main/java/com/barogagi/member/basic/join/mapper/JoinMapper.java index 4b17054..7929081 100644 --- a/src/main/java/com/barogagi/member/basic/join/mapper/JoinMapper.java +++ b/src/main/java/com/barogagi/member/basic/join/mapper/JoinMapper.java @@ -11,10 +11,10 @@ public interface JoinMapper { int insertMemberInfo(JoinDTO vo); // 아이디 중복 체크 - int checkUserId(JoinDTO vo); + int selectUserIdCnt(JoinDTO vo); // 닉네임 중복 체크 - int checkNickName(NickNameDTO dto); + int selectNicknameCnt(NickNameDTO dto); // 회원번호 중복 체크 int checkDuplicateMembershipNo(String membershipNo); diff --git a/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java b/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java new file mode 100644 index 0000000..2ef28de --- /dev/null +++ b/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java @@ -0,0 +1,285 @@ +package com.barogagi.member.basic.join.service; + +import com.barogagi.config.PasswordConfig; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.member.basic.join.dto.JoinDTO; +import com.barogagi.member.basic.join.dto.JoinRequestDTO; +import com.barogagi.member.basic.join.dto.NickNameDTO; +import com.barogagi.member.basic.join.exception.JoinException; +import com.barogagi.member.login.dto.RefreshTokenRequestDTO; +import com.barogagi.member.login.exception.InvalidRefreshTokenException; +import com.barogagi.member.login.service.AccountService; +import com.barogagi.member.login.service.AuthService; +import com.barogagi.response.ApiResponse; +import com.barogagi.util.EncryptUtil; +import com.barogagi.util.InputValidate; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.Validator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Service +public class BasicJoinService { + + private final Validator validator; + private final InputValidate inputValidate; + private final JoinService joinService; + private final AuthService authService; + private final AccountService accountService; + private final EncryptUtil encryptUtil; + private final PasswordConfig passwordConfig; + + @Autowired + public BasicJoinService( Validator validator + , InputValidate inputValidate + , JoinService joinService + , AuthService authService + , AccountService accountService + , EncryptUtil encryptUtil + , PasswordConfig passwordConfig) { + + this.validator = validator; + this.inputValidate = inputValidate; + this.joinService = joinService; + this.authService = authService; + this.accountService = accountService; + this.encryptUtil = encryptUtil; + this.passwordConfig = passwordConfig; + } + + // 닉네임 중복 체크 service + public ApiResponse checkNickname(String apiSecretKey, String nickname) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new JoinException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(nickname)) { + throw new JoinException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 적합한 닉네임인지 확인 + if(!validator.isValidNickname(nickname)) { + throw new JoinException( + ProcessResultCode.INVALID_NICKNAME.getResultCode(), + ProcessResultCode.INVALID_NICKNAME.getMessage() + ); + } + + // 4. 닉네임 중복 체크 + NickNameDTO nickNameDTO = new NickNameDTO(); + nickNameDTO.setNickName(nickname); + + int nickNameCnt = joinService.selectNicknameCnt(nickNameDTO); + if(nickNameCnt > 0) { + throw new JoinException( + ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), + ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.AVAILABLE_NICKNAME.getResultCode(), + ProcessResultCode.AVAILABLE_NICKNAME.getMessage() + ); + } + + // 아이디 중복 체크 service + public ApiResponse checkUserId(String apiSecretKey, String userId) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new JoinException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(userId)) { + throw new JoinException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 적합한 아이디인지 확인 + if(!validator.isValidId(userId)) { + throw new JoinException( + ProcessResultCode.INVALID_USER_ID.getResultCode(), + ProcessResultCode.INVALID_USER_ID.getMessage() + ); + } + + // 4. 아이디 중복 체크 + JoinDTO joinDTO = new JoinDTO(); + joinDTO.setUserId(userId); + + int checkUserId = joinService.selectUserIdCnt(joinDTO); + + if(checkUserId > 0) { + throw new JoinException( + ProcessResultCode.UNAVAILABLE_USER_ID.getResultCode(), + ProcessResultCode.UNAVAILABLE_USER_ID.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.AVAILABLE_USER_ID.getResultCode(), + ProcessResultCode.AVAILABLE_USER_ID.getMessage() + ); + } + + public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(joinRequestDTO.getApiSecretKey())) { + throw new JoinException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + // 필수 입력값(아이디, 비밀번호, 휴대전화번호 값이 빈 값이 아닌지 확인) + // 선택 입력값(이메일, 생년월일, 성별, 닉네임) + if(inputValidate.isEmpty(joinRequestDTO.getUserId()) + || inputValidate.isEmpty(joinRequestDTO.getPassword()) + || inputValidate.isEmpty(joinRequestDTO.getTel())) + { + throw new JoinException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 적합한 아이디인지 확인 + // 아이디, 비밀번호 적합성 검사 + if(!( + validator.isValidId(joinRequestDTO.getUserId()) + && validator.isValidPassword(joinRequestDTO.getPassword())) + ) { + throw new JoinException( + ProcessResultCode.INVALID_SIGN_UP.getResultCode(), + ProcessResultCode.INVALID_SIGN_UP.getMessage() + ); + } + + // 4. 암호화 + // 휴대전화번호, 비밀번호 암호화 + joinRequestDTO.setTel(encryptUtil.encrypt(joinRequestDTO.getTel().replaceAll("[^0-9]", ""))); + String encodedPassword = passwordConfig.passwordEncoder().encode(joinRequestDTO.getPassword()); + joinRequestDTO.setPassword(encodedPassword); + + // 이메일 값이 넘어오면 암호화 + if(!inputValidate.isEmpty(joinRequestDTO.getEmail())){ + joinRequestDTO.setEmail(encryptUtil.encrypt(joinRequestDTO.getEmail())); + } + + JoinDTO joinDTO = new JoinDTO(); + joinDTO.setUserId(joinRequestDTO.getUserId()); + joinDTO.setPassword(joinRequestDTO.getPassword()); + joinDTO.setEmail(joinRequestDTO.getEmail()); + + if(null != joinRequestDTO.getBirth()) { + joinRequestDTO.setBirth(joinRequestDTO.getBirth().replaceAll("[^0-9]", "")); + } + joinDTO.setBirth(joinRequestDTO.getBirth()); + joinDTO.setTel(joinRequestDTO.getTel()); + joinDTO.setGender(joinRequestDTO.getGender()); + joinDTO.setNickName(joinRequestDTO.getNickName()); + joinDTO.setJoinType("BASIC"); + + // 아이디 중복 검증 + int duplicateUserId = joinService.selectUserIdCnt(joinDTO); + + // 5. 아이디 중복 검증 + if(duplicateUserId > 0) { + throw new JoinException( + ProcessResultCode.UNAVAILABLE_USER_ID.getResultCode(), + ProcessResultCode.UNAVAILABLE_USER_ID.getMessage() + ); + } + + // 닉네임 값이 넘어올 경우 중복 검사 + if(!inputValidate.isEmpty(joinDTO.getNickName())) { + + // 닉네임 적합성 검사 + if(!validator.isValidNickname(joinRequestDTO.getNickName())) { + throw new JoinException( + ProcessResultCode.INVALID_NICKNAME.getResultCode(), + ProcessResultCode.INVALID_NICKNAME.getMessage() + ); + } + + NickNameDTO nickNameDTO = new NickNameDTO(); + nickNameDTO.setNickName(joinDTO.getNickName()); + int selectNicknameCnt = joinService.selectNicknameCnt(nickNameDTO); + + if(selectNicknameCnt > 0) { + throw new JoinException( + ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), + ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() + ); + } + } + + // 6. 회원 정보 저장 + int insertResult = joinService.insertMembershipInfo(joinDTO); + if(insertResult <= 0){ + throw new JoinException( + ProcessResultCode.FAIL_SIGN_UP.getResultCode(), + ProcessResultCode.FAIL_SIGN_UP.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_SIGN_UP.getResultCode(), + ProcessResultCode.SUCCESS_SIGN_UP.getMessage() + ); + } + + public ApiResponse deleteAccount(RefreshTokenRequestDTO refreshTokenRequestDTO) { + + // 1. refresh token이 공백 또는 null인지 확인 + if(inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { + throw new JoinException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage()); + } + + // 2. refresh token을 이용해서 membershipNo 구하기 + Map resultMap = authService.selectUserInfoByToken(refreshTokenRequestDTO.getRefreshToken()); + if(!resultMap.get("resultCode").equals("200")) { + throw new InvalidRefreshTokenException( + resultMap.get("resultCode"), + resultMap.get("message") + ); + } + + int deleteResult = accountService.deleteMyAccount(resultMap.get("membershipNo")); + if(deleteResult <= 0) { + throw new JoinException( + ProcessResultCode.FAIL_DELETE_ACCOUNT.getResultCode(), + ProcessResultCode.FAIL_DELETE_ACCOUNT.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_DELETE_ACCOUNT.getResultCode(), + ProcessResultCode.SUCCESS_DELETE_ACCOUNT.getMessage() + ); + } +} + + diff --git a/src/main/java/com/barogagi/member/basic/join/service/JoinService.java b/src/main/java/com/barogagi/member/basic/join/service/JoinService.java index 63bdc65..c33f06a 100644 --- a/src/main/java/com/barogagi/member/basic/join/service/JoinService.java +++ b/src/main/java/com/barogagi/member/basic/join/service/JoinService.java @@ -9,9 +9,7 @@ import org.springframework.stereotype.Service; import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; @Service public class JoinService { @@ -21,55 +19,16 @@ public class JoinService { private static final SecureRandom random = new SecureRandom(); private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - @Autowired - private JoinMapper joinMapper; - - // 회원가입 정보 저장 service - public int insertMembershipInfo(JoinDTO vo) throws Exception { - - int result = 0; - - String membershipNo = ""; - - while(true) { - // 랜덤 회원번호 생성 - membershipNo = this.createRandomStr(); - - logger.info("membershipNo.isEmpty()={}", membershipNo.isEmpty()); - if(membershipNo.isEmpty()) { - break; - } - - // 회원번호 중복 체크 - boolean checkDuplicateMembershipNo = this.checkDuplicateMemberNo(membershipNo); - logger.info("checkDuplicateMembershipNo={}", checkDuplicateMembershipNo); - if(!checkDuplicateMembershipNo) { - break; - } - } + private final JoinMapper joinMapper; - logger.info("membershipNo.isEmpty()={}", membershipNo.isEmpty()); - if(!membershipNo.isEmpty()) { // 회원번호가 비어있을 경우 저장 X - vo.setMembershipNo(membershipNo); - result = this.insertMemberInfo(vo); - } - - return result; - } - - // 회원가입 정보 저장 기능 - public int insertMemberInfo(JoinDTO vo) throws Exception{ - return joinMapper.insertMemberInfo(vo); - } - - // 아이디 중복 체크 - public int checkUserId(JoinDTO vo) throws Exception{ - return joinMapper.checkUserId(vo); + @Autowired + public JoinService(JoinMapper joinMapper) { + this.joinMapper = joinMapper; } - // 닉네임 중복 체크 - public int checkNickName(NickNameDTO nickNameDTO) throws Exception{ - return joinMapper.checkNickName(nickNameDTO); + // 닉네임 개수 구하기 + public int selectNicknameCnt(NickNameDTO nickNameDTO) { + return joinMapper.selectNicknameCnt(nickNameDTO); } // 회원번호 랜덤값 생성 @@ -110,7 +69,7 @@ public String createRandomStr() { } // 회원번호 중복 체크 - public boolean checkDuplicateMemberNo(String membershipNo) throws Exception { + public boolean checkDuplicateMemberNo(String membershipNo) { boolean duplicateFlag = false; int membershipNoCnt = joinMapper.checkDuplicateMembershipNo(membershipNo); @@ -122,4 +81,47 @@ public boolean checkDuplicateMemberNo(String membershipNo) throws Exception { return duplicateFlag; } + + // 회원가입 정보 저장 service + public int insertMembershipInfo(JoinDTO vo) { + + int result = 0; + + String membershipNo = ""; + + while(true) { + // 랜덤 회원번호 생성 + membershipNo = this.createRandomStr(); + + logger.info("membershipNo.isEmpty()={}", membershipNo.isEmpty()); + if(membershipNo.isEmpty()) { + break; + } + + // 회원번호 중복 체크 + boolean checkDuplicateMembershipNo = this.checkDuplicateMemberNo(membershipNo); + logger.info("checkDuplicateMembershipNo={}", checkDuplicateMembershipNo); + if(!checkDuplicateMembershipNo) { + break; + } + } + + logger.info("membershipNo.isEmpty()={}", membershipNo.isEmpty()); + if(!membershipNo.isEmpty()) { // 회원번호가 비어있을 경우 저장 X + vo.setMembershipNo(membershipNo); + result = this.insertMemberInfo(vo); + } + + return result; + } + + // 회원가입 정보 저장 기능 + public int insertMemberInfo(JoinDTO vo) { + return joinMapper.insertMemberInfo(vo); + } + + // 아이디 개수 구하기 + public int selectUserIdCnt(JoinDTO vo) { + return joinMapper.selectUserIdCnt(vo); + } } diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index ec586dd..b5e7342 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -1,193 +1,47 @@ package com.barogagi.member.info.controller; -import com.barogagi.member.basic.join.dto.NickNameDTO; -import com.barogagi.member.basic.join.service.JoinService; -import com.barogagi.member.info.exception.MemberInfoException; -import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.dto.MemberRequestDTO; import com.barogagi.member.info.service.MemberService; import com.barogagi.response.ApiResponse; -import com.barogagi.util.EncryptUtil; -import com.barogagi.util.MembershipUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; -import java.util.Map; - @Tag(name = "회원 정보", description = "회원 정보 관련 API") @RestController @RequestMapping("/api/v1/members") public class InfoController { - private static final Logger logger = LoggerFactory.getLogger(InfoController.class); - private final MemberService memberService; - private final JoinService joinService; - - private final EncryptUtil encryptUtil; - private final MembershipUtil membershipUtil; - - public InfoController(MemberService memberService, - JoinService joinService, - EncryptUtil encryptUtil, - MembershipUtil membershipUtil) { + public InfoController(MemberService memberService) { this.memberService = memberService; - this.joinService = joinService; - this.encryptUtil = encryptUtil; - this.membershipUtil = membershipUtil; } @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 정보 조회가 완료되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping public ApiResponse selectMemberInfo(HttpServletRequest request) { - logger.info("CALL /api/v1/members"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - // 회원번호 구하기 - Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), - String.valueOf(membershipNoInfo.get("message"))); - } - - String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); - - // 회원 정보 조회 - Member memberInfo = memberService.findByMembershipNo(membershipNo); - if(null == memberInfo) { - throw new MemberInfoException("402", "해당 사용자에 대한 정보가 존재하지 않습니다."); - } - - // 이메일 복호화 - memberInfo.setEmail(encryptUtil.decrypt(memberInfo.getEmail())); - - // 전화번호 복호화 - memberInfo.setTel(encryptUtil.decrypt(memberInfo.getTel())); - - // 비밀번호는 보내주지 않는다 - memberInfo.setPassword(""); - - resultCode = "200"; - message = "회원 정보 조회가 완료되었습니다."; - apiResponse.setData(memberInfo); - - } catch (MemberInfoException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return memberService.selectMemberInfo(request); } @Operation(summary = "회원 정보 수정", description = "회원 정보 조회 수정입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "이미 해당 닉네임이 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 닉네임 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자 정보 수정 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용자 정보 수정 완료하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PatchMapping - public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody MemberRequestDTO memberRequestDto) { - logger.info("CALL /api/v1/members"); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - - logger.info("param gender={}", memberRequestDto.getGender()); - logger.info("param nickName={}", memberRequestDto.getNickName()); - - // 회원번호 구하기 - Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MemberInfoException(String.valueOf(membershipNoInfo.get("resultCode")), - String.valueOf(membershipNoInfo.get("message"))); - } - - String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); - logger.info("@@ membershipNo={}", membershipNo); - - // 회원 정보 조회 - Member memberInfo = memberService.findByMembershipNo(membershipNo); - if(null == memberInfo) { - throw new MemberInfoException("402", "해당 사용자에 대한 정보가 존재하지 않습니다."); - } - - // 생년월일 - if(!memberRequestDto.getBirth().isEmpty()) { - memberInfo.setBirth(memberRequestDto.getBirth().replaceAll("[^0-9]", "")); - } - - // 성별 (M : 남 / W : 여) - if(!memberRequestDto.getGender().isEmpty()) { - memberInfo.setGender(memberRequestDto.getGender()); - } - - // 닉네임(중복X) - if(!memberRequestDto.getNickName().isEmpty()) { - NickNameDTO nickNameRequest = new NickNameDTO(); - nickNameRequest.setNickName(memberRequestDto.getNickName()); - int nickNameCnt = joinService.checkNickName(nickNameRequest); - - logger.info("@@ nickNameCnt={}", nickNameCnt); - if(nickNameCnt > 0) { - throw new MemberInfoException("403", "이미 해당 닉네임이 존재합니다."); - } - - memberInfo.setNickName(memberRequestDto.getNickName()); - } - - int updateMemberInfo = memberService.updateMemberInfo(memberInfo); - logger.info("@@ updateMemberInfo={}", updateMemberInfo); - if(updateMemberInfo <= 0) { - throw new MemberInfoException("404", "사용자 정보 수정 실패하였습니다."); - } - - // 사용자 정보 수정 성공 - resultCode = "200"; - message = "사용자 정보 수정 완료하였습니다."; - - } catch (MemberInfoException ex) { - resultCode = ex.getResultCode(); - message = ex.getMessage(); - - } catch (Exception e) { - logger.error("error", e); - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + public ApiResponse updateMemberInfo(HttpServletRequest request, @RequestBody MemberRequestDTO memberRequestDTO) { + return memberService.updateMemberProcess(request, memberRequestDTO); } } diff --git a/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java index 3e7c3f7..8888ead 100644 --- a/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java +++ b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java @@ -1,13 +1,12 @@ package com.barogagi.member.info.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class MemberInfoException extends RuntimeException{ - private final String resultCode; +public class MemberInfoException extends BusinessException { public MemberInfoException(String resultCode, String message) { - super(message); - this.resultCode = resultCode; + super(resultCode, message); } } diff --git a/src/main/java/com/barogagi/member/info/service/MemberService.java b/src/main/java/com/barogagi/member/info/service/MemberService.java index c791062..1efbe21 100644 --- a/src/main/java/com/barogagi/member/info/service/MemberService.java +++ b/src/main/java/com/barogagi/member/info/service/MemberService.java @@ -1,25 +1,154 @@ package com.barogagi.member.info.service; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.member.basic.join.dto.NickNameDTO; +import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.info.dto.Member; +import com.barogagi.member.info.dto.MemberRequestDTO; +import com.barogagi.member.info.exception.MemberInfoException; import com.barogagi.member.info.mapper.MemberMapper; +import com.barogagi.response.ApiResponse; +import com.barogagi.util.EncryptUtil; +import com.barogagi.util.InputValidate; +import com.barogagi.util.MembershipUtil; +import com.barogagi.config.resultCode.ResultCode; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Map; + @Service public class MemberService { + private final MemberMapper memberMapper; + private final MembershipUtil membershipUtil; + private final EncryptUtil encryptUtil; + private final JoinService joinService; + private final InputValidate inputValidate; + @Autowired - private MemberMapper memberMapper; + public MemberService(MemberMapper memberMapper, + MembershipUtil membershipUtil, + EncryptUtil encryptUtil, + JoinService joinService, + InputValidate inputValidate) + { + this.memberMapper = memberMapper; + this.membershipUtil = membershipUtil; + this.encryptUtil = encryptUtil; + this.joinService = joinService; + this.inputValidate = inputValidate; + } + + public ApiResponse selectMemberInfo(HttpServletRequest request) { + + // 1. 회원번호 구하기 + Map membershipNoInfo = membershipUtil.membershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MemberInfoException( + String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message")) + ); + } + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); + + // 2. 회원 정보 조회 + Member memberInfo = this.findByMembershipNo(membershipNo); + if(null == memberInfo) { + throw new MemberInfoException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + ); + } + + // 이메일 복호화 + memberInfo.setEmail(encryptUtil.decrypt(memberInfo.getEmail())); + + // 전화번호 복호화 + memberInfo.setTel(encryptUtil.decrypt(memberInfo.getTel())); + + // 비밀번호는 보내주지 않는다 + memberInfo.setPassword(""); + + return ApiResponse.resultData( + memberInfo, + ProcessResultCode.FOUND_USER_INFO.getResultCode(), + ProcessResultCode.FOUND_USER_INFO.getMessage() + ); + } + + public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequestDTO memberRequestDTO) { + + // 1. 회원번호 구하기 + Map membershipNoInfo = membershipUtil.membershipNoService(request); + if(!membershipNoInfo.get("resultCode").equals("200")) { + throw new MemberInfoException( + String.valueOf(membershipNoInfo.get("resultCode")), + String.valueOf(membershipNoInfo.get("message"))); + } + + String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); + + // 2. 회원 정보 조회 + Member memberInfo = this.findByMembershipNo(membershipNo); + if(null == memberInfo) { + throw new MemberInfoException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + ); + } + + // 3. 데이터 처리 + // 생년월일 + if(!inputValidate.isEmpty(memberRequestDTO.getBirth())) { + memberInfo.setBirth(memberRequestDTO.getBirth().replaceAll("[^0-9]", "")); + } + + // 성별 (M : 남 / W : 여) + if(!inputValidate.isEmpty(memberRequestDTO.getGender())) { + memberInfo.setGender(memberRequestDTO.getGender()); + } + + // 닉네임(중복X) + if(!inputValidate.isEmpty(memberRequestDTO.getNickName())) { + NickNameDTO nickNameRequest = new NickNameDTO(); + nickNameRequest.setNickName(memberRequestDTO.getNickName()); + int nickNameCnt = joinService.selectNicknameCnt(nickNameRequest); + + if(nickNameCnt > 0) { + throw new MemberInfoException( + ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), + ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() + ); + } + + memberInfo.setNickName(memberRequestDTO.getNickName()); + } + + int updateMemberInfo = this.updateMemberInfo(memberInfo); + if(updateMemberInfo <= 0) { + throw new MemberInfoException( + ProcessResultCode.FAIL_UPDATE_USER_INFO.getResultCode(), + ProcessResultCode.FAIL_UPDATE_USER_INFO.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_UPDATE_USER_INFO.getResultCode(), + ProcessResultCode.SUCCESS_UPDATE_USER_INFO.getMessage() + ); + } - public Member findByMembershipNo(String membershipNo) throws Exception { + public Member findByMembershipNo(String membershipNo) { return memberMapper.findByMembershipNo(membershipNo); } - public Member selectUserMembershipInfo(String userId) throws Exception { + public Member selectUserMembershipInfo(String userId) { return memberMapper.selectUserMembershipInfo(userId); } - public int updateMemberInfo(Member member) throws Exception { + public int updateMemberInfo(Member member) { return memberMapper.updateMemberInfo(member); } } diff --git a/src/main/java/com/barogagi/member/login/controller/AuthController.java b/src/main/java/com/barogagi/member/login/controller/AuthController.java index 3d024e2..e69de29 100644 --- a/src/main/java/com/barogagi/member/login/controller/AuthController.java +++ b/src/main/java/com/barogagi/member/login/controller/AuthController.java @@ -1,36 +0,0 @@ -package com.barogagi.member.login.controller; - -import com.barogagi.member.login.dto.*; -import com.barogagi.member.login.exception.InvalidRefreshTokenException; -import com.barogagi.member.login.service.AccountService; -import com.barogagi.member.login.service.AuthService; -import com.barogagi.response.ApiResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.Map; -import java.util.Optional; - -@Tag(name = "TOKEN 재발급, 로그아웃, 탈퇴", description = "TOKEN 재발급, 로그아웃, 탈퇴 관련 API") -@RestController -@RequestMapping("/auth") -public class AuthController { - - private static final Logger logger = LoggerFactory.getLogger(AuthController.class); - - private final AuthService authService; - private final AccountService accountService; - - public AuthController(AuthService authService, - AccountService accountService) { - this.authService = authService; - this.accountService = accountService; - } -} - - diff --git a/src/main/java/com/barogagi/member/login/controller/LoginController.java b/src/main/java/com/barogagi/member/login/controller/LoginController.java index 3e56b77..c33fa6e 100644 --- a/src/main/java/com/barogagi/member/login/controller/LoginController.java +++ b/src/main/java/com/barogagi/member/login/controller/LoginController.java @@ -1,73 +1,29 @@ package com.barogagi.member.login.controller; -import com.barogagi.config.PasswordConfig; -import com.barogagi.member.info.dto.Member; -import com.barogagi.member.info.service.MemberService; import com.barogagi.member.login.dto.*; -import com.barogagi.member.login.exception.InvalidRefreshTokenException; -import com.barogagi.member.login.exception.LoginException; -import com.barogagi.member.login.service.AuthService; import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; -import com.barogagi.util.EncryptUtil; -import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; -import org.springframework.http.ResponseEntity; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; -import java.util.*; - @Tag(name = "일반 로그인 & 토큰 재발급", description = "일반 로그인, 토큰 재발급 관련 API") @RestController @RequestMapping("/api/v1/auth") public class LoginController { - private static final Logger logger = LoggerFactory.getLogger(LoginController.class); - - private final InputValidate inputValidate; - - private final EncryptUtil encryptUtil; private final LoginService loginService; - private final MemberService memberService; - private final AuthService authService; - - private final PasswordEncoder passwordEncoder; - - private final PasswordConfig passwordConfig; - - private final String API_SECRET_KEY; @Autowired - public LoginController(Environment environment, - InputValidate inputValidate, - EncryptUtil encryptUtil, - LoginService loginService, - MemberService memberService, - AuthService authService, - PasswordEncoder passwordEncoder, - PasswordConfig passwordConfig){ - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); - - this.inputValidate = inputValidate; - this.encryptUtil = encryptUtil; + public LoginController(LoginService loginService){ this.loginService = loginService; - this.memberService = memberService; - this.authService = authService; - this.passwordEncoder = passwordEncoder; - this.passwordConfig = passwordConfig; } @Operation(summary = "로그인", description = "로그인 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "로그인에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "로그인이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "로그인에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @@ -75,254 +31,44 @@ public LoginController(Environment environment, }) @PostMapping("/login") public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ - - logger.info("CALL /api/v1/auth/login"); - logger.info("[input] API_SECRET_KEY={}", loginRequestDTO.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if (loginRequestDTO.getApiSecretKey().equals(API_SECRET_KEY)) { - - if (inputValidate.isEmpty(loginRequestDTO.getUserId()) || inputValidate.isEmpty(loginRequestDTO.getPassword())) { - throw new LoginException("101", "로그인이 불가능합니다."); - } else { - - logger.info("@@@ userId={}", loginRequestDTO.getUserId()); - logger.info("@@@ password={}", loginRequestDTO.getPassword()); - - // 로그인 성공 -> 사용자 정보 조회(membershipNo, userId 등 토큰에 넣을 값) - Member member = memberService.selectUserMembershipInfo(loginRequestDTO.getUserId()); - if (null == member) { - throw new LoginException("102", "회원 정보가 존재하지 않습니다."); - } - - boolean ok = passwordEncoder.matches(loginRequestDTO.getPassword(), member.getPassword()); - - logger.info("@@ ok={}", ok); - if(!ok) { - throw new LoginException("103", "로그인에 실패하였습니다."); - } - - String userId = member.getUserId(); - - // ACCESS, REFRESH TOKEN 생싱 & REFRESH TOKEN 저장 - LoginResponse loginResponse = authService.loginAfterSignup(userId, "web-basic"); - Map loginResponseMap = Map.of( - "accessToken", loginResponse.tokens().accessToken(), - "accessTokenExpiresIn", loginResponse.tokens().accessTokenExpiresIn(), - "userId", userId, - "membershipNo", loginResponse.membershipNo(), - "refreshToken", loginResponse.tokens().refreshToken(), - "refreshTokenExpiresIn", loginResponse.tokens().refreshTokenExpiresIn() - ); - - resultCode = "200"; - message = "로그인에 성공하였습니다."; - apiResponse.setData(loginResponseMap); - } - - } else { - throw new LoginException("100", "잘못된 접근입니다."); - } - } catch (LoginException ex) { - resultCode = ex.getCode(); - message = ex.getMessage(); - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return loginService.login(loginRequestDTO); } @Operation(summary = "아이디 찾기 기능", description = "아이디 찾기 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 전화번호로 가입된 아이디입니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "전화번호가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 전화번호로 가입된 아이디가 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "해당 전화번호로 가입된 계정이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/find-user") - public ApiResponse searchUserId(@RequestBody SearchUserIdDTO searchUserIdRequestDTO){ - - logger.info("CALL /api/v1/auth/find-user"); - logger.info("[input] API_SECRET_KEY={}", searchUserIdRequestDTO.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(searchUserIdRequestDTO.getApiSecretKey().equals(API_SECRET_KEY)){ - - if(inputValidate.isEmpty(searchUserIdRequestDTO.getTel())){ - resultCode = "101"; - message = "전화번호가 존재하지 않습니다."; - - } else { - - searchUserIdRequestDTO.setTel(encryptUtil.encrypt(searchUserIdRequestDTO.getTel())); - logger.info("tel={}", searchUserIdRequestDTO.getTel()); - List myUserIdList = loginService.myUserIdList(searchUserIdRequestDTO); - - int userIdCnt = myUserIdList.size(); - if(userIdCnt > 0){ - resultCode = "200"; - message = "해당 전화번호로 가입된 아이디입니다."; - - List> userIdList = new ArrayList<>(); - for(UserIdDTO vo : myUserIdList){ - Map map = new HashMap<>(); - map.put("userId", vo.getUserId()); - userIdList.add(map); - } - apiResponse.setData(userIdList); - - } else { - resultCode = "201"; - message = "해당 전화번호로 가입된 계정이 존재하지 않습니다."; - } - } - - } else{ - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + public ApiResponse findUser(@RequestBody SearchUserIdDTO searchUserIdRequestDTO){ + return loginService.findUser(searchUserIdRequestDTO); } @Operation(summary = "비밀번호 재설정 기능", description = "비밀번호 재설정 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "비밀번호 재설정에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "아이디, 비밀번호 값이 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "비밀번호 재설정에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/password-reset/confirm") public ApiResponse updatePassword(@RequestBody LoginDTO vo){ - - logger.info("CALL /api/v1/auth/password-reset/confirm"); - logger.info("[input] API_SECRET_KEY={}", vo.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(vo.getApiSecretKey().equals(API_SECRET_KEY)){ - - if(inputValidate.isEmpty(vo.getUserId()) || inputValidate.isEmpty(vo.getPassword())){ - resultCode = "101"; - message = "아이디, 비밀번호 값이 없습니다."; - - } else { - // 비밀번호 암호화 - String encodedPassword = passwordConfig.passwordEncoder().encode(vo.getPassword()); - vo.setPassword(encodedPassword); - - // 비밀번호 update - int updatePassword = loginService.updatePassword(vo); - logger.info("@@ updatePassword={}", updatePassword); - - if(updatePassword > 0){ - resultCode = "200"; - message = "비밀번호 재설정에 성공하였습니다."; - } else{ - resultCode = "300"; - message = "비밀번호 재설정에 실패하였습니다."; - } - } - - } else{ - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - return apiResponse; + return loginService.updatePasswordProcess(vo); } @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") @PostMapping("/token/refresh") - public ResponseEntity> refresh( - @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body - ) { - - logger.info("CALL /api/v1/auth/token/refresh"); - - try { - String rt = Optional.ofNullable(refreshHeader) - .or(() -> Optional.ofNullable(body == null ? null : body.get("refreshToken"))) - .orElse(null); - - if (rt == null || rt.isBlank()) { - return ResponseEntity.status(401).body(Map.of("error", "refresh_required")); - } - - TokenPair pair = authService.rotate(rt); // ❗️핵심 로직 (아래 2) 참조) - - return ResponseEntity.ok(Map.of( - "accessToken", pair.accessToken(), - "accessTokenExpiresIn", pair.accessTokenExpiresIn(), - "refreshToken", pair.refreshToken(), - "refreshTokenExpiresIn", pair.refreshTokenExpiresIn() - )); - - } catch (InvalidRefreshTokenException e) { - return ResponseEntity.status(HttpServletResponse.SC_UNAUTHORIZED) - .body(Map.of( - "resultCode", "400", - "errorCode", e.getCode(), - "message", e.getMessage(), - "needLogin", true - )); - } + public ApiResponse refresh(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { + return loginService.refreshToken(refreshTokenRequestDTO); } - /** - * 현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE - * 입력 경로: - * - 헤더: Refresh-Token: - * - 바디: { "refreshToken": "" } - */ @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") @PostMapping("/logout") - public ResponseEntity> logout( - @RequestHeader(value = "Refresh-Token", required = false) String refreshHeader, - @RequestBody(required = false) Map body - ) { - String refresh = refreshHeader; - if ((refresh == null || refresh.isBlank()) && body != null) { - refresh = body.get("refreshToken"); - } - if (refresh != null && !refresh.isBlank()) { - authService.logout(refresh); // DB REVOKE - } - return ResponseEntity.ok(Map.of("resultCode", "200", "message", "로그아웃 되었습니다.")); + public ApiResponse logout(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { + return loginService.logout(refreshTokenRequestDTO); } } diff --git a/src/main/java/com/barogagi/member/login/dto/RefreshTokenRequestDTO.java b/src/main/java/com/barogagi/member/login/dto/RefreshTokenRequestDTO.java new file mode 100644 index 0000000..9a573d4 --- /dev/null +++ b/src/main/java/com/barogagi/member/login/dto/RefreshTokenRequestDTO.java @@ -0,0 +1,12 @@ +package com.barogagi.member.login.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RefreshTokenRequestDTO { + + private String refreshToken = ""; + +} diff --git a/src/main/java/com/barogagi/member/login/dto/TokenPair.java b/src/main/java/com/barogagi/member/login/dto/TokenPair.java index 7554a95..8e3da4e 100644 --- a/src/main/java/com/barogagi/member/login/dto/TokenPair.java +++ b/src/main/java/com/barogagi/member/login/dto/TokenPair.java @@ -2,5 +2,6 @@ public record TokenPair( String accessToken, long accessTokenExpiresIn, - String refreshToken, long refreshTokenExpiresIn + String refreshToken, long refreshTokenExpiresIn, + String resultCode, String message ) {} diff --git a/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java b/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java index c330e3e..2c89470 100644 --- a/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java +++ b/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java @@ -1,14 +1,12 @@ package com.barogagi.member.login.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class InvalidRefreshTokenException extends RuntimeException{ +public class InvalidRefreshTokenException extends BusinessException { - private final String code; - - public InvalidRefreshTokenException(String code, String message) { - super(message); - this.code = code; + public InvalidRefreshTokenException(String resultCode, String message) { + super(resultCode, message); } } diff --git a/src/main/java/com/barogagi/member/login/exception/LoginException.java b/src/main/java/com/barogagi/member/login/exception/LoginException.java index 3dda611..84a44f9 100644 --- a/src/main/java/com/barogagi/member/login/exception/LoginException.java +++ b/src/main/java/com/barogagi/member/login/exception/LoginException.java @@ -1,13 +1,12 @@ package com.barogagi.member.login.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class LoginException extends RuntimeException { - private final String code; +public class LoginException extends BusinessException { - public LoginException(String code, String message) { - super(message); - this.code = code; + public LoginException(String resultCode, String message) { + super(resultCode, message); } } diff --git a/src/main/java/com/barogagi/member/login/service/AuthService.java b/src/main/java/com/barogagi/member/login/service/AuthService.java index 78474c2..148df4b 100644 --- a/src/main/java/com/barogagi/member/login/service/AuthService.java +++ b/src/main/java/com/barogagi/member/login/service/AuthService.java @@ -1,5 +1,6 @@ package com.barogagi.member.login.service; +import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.login.dto.*; import com.barogagi.member.login.entity.RefreshToken; import com.barogagi.member.login.entity.UserMembership; @@ -76,14 +77,21 @@ public LoginResponse login(LoginRequest req) { refreshRepo.save(rt); return new LoginResponse( - new TokenPair(access, accessExp, refresh, refreshExp), + new TokenPair( + access, + accessExp, + refresh, + refreshExp, + ProcessResultCode.SUCCESS_LOGIN.getResultCode(), + ProcessResultCode.SUCCESS_LOGIN.getMessage() + ), no, u.getUserId(), u.getJoinType() ); } /** 구글/네이버 등 OAuth 가입 직후: userId로 바로 토큰 발급 (비밀번호 검증 없음) */ public LoginResponse loginAfterSignup(String userId, String deviceId) { - var u = userRepo.findByUserId(userId) + UserMembership u = userRepo.findByUserId(userId) .orElseThrow(() -> new RuntimeException("USER_NOT_FOUND")); String no = u.getMembershipNo(); @@ -91,7 +99,7 @@ public LoginResponse loginAfterSignup(String userId, String deviceId) { String refresh = jwt.generateRefreshToken(no, deviceId != null ? deviceId : "web-oauth"); // Refresh 저장(VALID) - var rt = new RefreshToken(); + RefreshToken rt = new RefreshToken(); rt.setMembershipNo(no); rt.setDeviceId(deviceId != null ? deviceId : "web-oauth"); rt.setToken(refresh); @@ -101,13 +109,21 @@ public LoginResponse loginAfterSignup(String userId, String deviceId) { refreshRepo.save(rt); return new LoginResponse( - new TokenPair(access, accessExp, refresh, refreshExp), + new TokenPair( + access, + accessExp, + refresh, + refreshExp, + ProcessResultCode.SUCCESS_REFRESH_TOKEN.getResultCode(), + ProcessResultCode.SUCCESS_REFRESH_TOKEN.getMessage() + ), no, u.getUserId(), u.getJoinType() ); } @Transactional public TokenPair rotate(String refreshToken) { + try { if (!jwt.isTokenValid(refreshToken) || !jwt.isRefreshToken(refreshToken)) { throw new BadCredentialsException("invalid_refresh_token"); @@ -116,6 +132,9 @@ public TokenPair rotate(String refreshToken) { throw new BadCredentialsException("invalid_refresh_token"); } + String newAccess = ""; + String newRefresh = ""; + String membershipNo = jwt.getMembershipNo(refreshToken); String deviceId = jwt.getDeviceId(refreshToken); @@ -124,46 +143,67 @@ public TokenPair rotate(String refreshToken) { deviceId = "web-oauth"; } - // 현재 리프레시가 DB에 VALID로 존재하는지 확인 - RefreshToken current = refreshRepo.findByTokenAndStatus( - refreshToken, "VALID" - ).orElseThrow(() -> new InvalidRefreshTokenException("refresh_not_found_or_revoked", "로그인을 진행해주세요.")); + try { - // 만료 체크 - if (current.getExpiresAt().isBefore(LocalDateTime.now())) { - current.setStatus("REVOKED"); - refreshRepo.save(current); - throw new InvalidRefreshTokenException("refresh_expired", "로그인을 다시 진행해주세요."); - } + // 현재 리프레시가 DB에 VALID로 존재하는지 확인 + RefreshToken current = refreshRepo.findByTokenAndStatus(refreshToken, "VALID") + .orElseThrow(() -> new InvalidRefreshTokenException( + ProcessResultCode.REQUIRED_LOGIN.getResultCode(), + ProcessResultCode.REQUIRED_LOGIN.getMessage() + )); + + // 만료 체크 + if (current.getExpiresAt().isBefore(LocalDateTime.now())) { + current.setStatus("REVOKED"); + refreshRepo.save(current); + throw new InvalidRefreshTokenException( + ProcessResultCode.REQUIRED_RE_LOGIN.getResultCode(), + ProcessResultCode.REQUIRED_RE_LOGIN.getMessage() + ); + } - // 같은 멤버/디바이스의 기존 VALID 토큰들 모두 REVOKE (동시 세션 차단용) - var olds = refreshRepo.findByMembershipNoAndDeviceIdAndStatus( - membershipNo, deviceId, "VALID" - ); - for (var o : olds) { - o.setStatus("REVOKED"); + // 같은 멤버/디바이스의 기존 VALID 토큰들 모두 REVOKE (동시 세션 차단용) + List olds = refreshRepo.findByMembershipNoAndDeviceIdAndStatus(membershipNo, deviceId, "VALID"); + for (RefreshToken o : olds) { + o.setStatus("REVOKED"); + } + refreshRepo.saveAll(olds); + + // 새 토큰 발급 + UserMembership user = userRepo.findById(membershipNo) + .orElseThrow(() -> new InvalidRefreshTokenException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + )); + + newAccess = jwt.generateAccessToken(membershipNo, user.getUserId()); + newRefresh = jwt.generateRefreshToken(membershipNo, deviceId); + + RefreshToken next = new RefreshToken(); + next.setMembershipNo(membershipNo); + next.setDeviceId(deviceId); + next.setToken(newRefresh); + next.setStatus("VALID"); + next.setCreatedAt(LocalDateTime.now()); + next.setExpiresAt(LocalDateTime.now().plusSeconds(jwt.getRefreshExpSeconds())); + refreshRepo.save(next); + + } catch (InvalidRefreshTokenException ex) { + return new TokenPair( + "", + 0, + "", + 0, + ex.getResultCode(), + ex.getMessage() + ); } - refreshRepo.saveAll(olds); - - // 새 토큰 발급 - var user = userRepo.findById(membershipNo) - .orElseThrow(() -> new InvalidRefreshTokenException("user_not_found", "회원 정보가 존재하지 않습니다.")); - - String newAccess = jwt.generateAccessToken(membershipNo, user.getUserId()); - String newRefresh = jwt.generateRefreshToken(membershipNo, deviceId); - - RefreshToken next = new RefreshToken(); - next.setMembershipNo(membershipNo); - next.setDeviceId(deviceId); - next.setToken(newRefresh); - next.setStatus("VALID"); - next.setCreatedAt(LocalDateTime.now()); - next.setExpiresAt(LocalDateTime.now().plusSeconds(jwt.getRefreshExpSeconds())); - refreshRepo.save(next); return new TokenPair( newAccess, jwt.getAccessExpSeconds(), - newRefresh, jwt.getRefreshExpSeconds() + newRefresh, jwt.getRefreshExpSeconds(), + ProcessResultCode.SUCCESS_REFRESH_TOKEN.getResultCode(), + ProcessResultCode.SUCCESS_REFRESH_TOKEN.getMessage() ); } @@ -178,7 +218,7 @@ public void logout(String refreshToken) { List tokens = refreshRepo .findByMembershipNoAndDeviceIdAndStatus(membershipNo, deviceId, "VALID"); - for (var t : tokens) t.setStatus("REVOKED"); + for (RefreshToken t : tokens) t.setStatus("REVOKED"); if (!tokens.isEmpty()) refreshRepo.saveAll(tokens); } @@ -218,7 +258,7 @@ public Map selectUserInfoByToken(String refreshToken) { returnMap.put("membershipNo", membershipNo); } catch (InvalidRefreshTokenException e) { - resultCode = e.getCode(); + resultCode = e.getResultCode(); message = e.getMessage(); } finally { returnMap.put("resultCode", resultCode); diff --git a/src/main/java/com/barogagi/member/login/service/LoginService.java b/src/main/java/com/barogagi/member/login/service/LoginService.java index 29916a7..8300cc5 100644 --- a/src/main/java/com/barogagi/member/login/service/LoginService.java +++ b/src/main/java/com/barogagi/member/login/service/LoginService.java @@ -1,23 +1,248 @@ package com.barogagi.member.login.service; -import com.barogagi.member.login.dto.LoginDTO; -import com.barogagi.member.login.dto.LoginVO; -import com.barogagi.member.login.dto.SearchUserIdDTO; -import com.barogagi.member.login.dto.UserIdDTO; +import com.barogagi.config.PasswordConfig; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.member.info.dto.Member; +import com.barogagi.member.info.service.MemberService; +import com.barogagi.member.login.dto.*; +import com.barogagi.member.login.exception.LoginException; import com.barogagi.member.login.mapper.LoginMapper; +import com.barogagi.response.ApiResponse; +import com.barogagi.util.EncryptUtil; +import com.barogagi.util.InputValidate; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.Validator; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Service public class LoginService { private final LoginMapper loginMapper; + private final Validator validator; + private final InputValidate inputValidate; + private final EncryptUtil encryptUtil; + private final PasswordConfig passwordConfig; + private final MemberService memberService; + private final PasswordEncoder passwordEncoder; + private final AuthService authService; @Autowired - public LoginService(LoginMapper loginMapper){ + public LoginService( + LoginMapper loginMapper, + Validator validator, + InputValidate inputValidate, + EncryptUtil encryptUtil, + PasswordConfig passwordConfig, + MemberService memberService, + PasswordEncoder passwordEncoder, + AuthService authService + ) + { this.loginMapper = loginMapper; + this.validator = validator; + this.inputValidate = inputValidate; + this.encryptUtil = encryptUtil; + this.passwordConfig = passwordConfig; + this.memberService = memberService; + this.passwordEncoder = passwordEncoder; + this.authService = authService; + } + + public ApiResponse findUser(SearchUserIdDTO searchUserIdDTO) { + + String resultCode = ""; + String message = ""; + List userIdList = null; + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(searchUserIdDTO.getApiSecretKey())) { + throw new LoginException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(searchUserIdDTO.getTel())) { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + searchUserIdDTO.setTel(encryptUtil.encrypt(searchUserIdDTO.getTel())); + List searchIdList = this.myUserIdList(searchUserIdDTO); + + if(searchIdList.isEmpty()) { + resultCode = ProcessResultCode.NOT_FOUND_ACCOUNT.getResultCode(); + message = ProcessResultCode.NOT_FOUND_ACCOUNT.getMessage(); + } else { + resultCode = ProcessResultCode.FOUND_ACCOUNT.getResultCode(); + message = ProcessResultCode.FOUND_ACCOUNT.getMessage(); + userIdList = searchIdList; + } + + return ApiResponse.resultData(userIdList, resultCode, message); + } + + public ApiResponse updatePasswordProcess(LoginDTO loginDTO) { + String resultCode = ""; + String message = ""; + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(loginDTO.getApiSecretKey())) { + throw new LoginException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if ( + inputValidate.isEmpty(loginDTO.getUserId()) + || inputValidate.isEmpty(loginDTO.getPassword()) + ) { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 비밀번호 암호화 + loginDTO.setPassword(passwordEncoder.encode(loginDTO.getPassword())); + + // 4. 비밀번호 update + int updatePassword = this.updatePassword(loginDTO); + if(updatePassword > 0) { + resultCode = ProcessResultCode.SUCCESS_UPDATE_PASSWORD.getResultCode(); + message = ProcessResultCode.SUCCESS_UPDATE_PASSWORD.getMessage(); + } else { + throw new LoginException( + ProcessResultCode.FAIL_UPDATE_PASSWORD.getResultCode(), + ProcessResultCode.FAIL_UPDATE_PASSWORD.getMessage() + ); + } + + return ApiResponse.result(resultCode, message); + } + + public ApiResponse login(LoginDTO loginDTO) { + + String resultCode = ""; + String message = ""; + Map dataMap = new HashMap<>(); + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(loginDTO.getApiSecretKey())) { + throw new LoginException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if( + inputValidate.isEmpty(loginDTO.getUserId()) + || inputValidate.isEmpty(loginDTO.getPassword()) + ) + { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 아이디로 회원정보 조회 + Member member = memberService.selectUserMembershipInfo(loginDTO.getUserId()); + if (null == member) { + throw new LoginException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + ); + } + + // 4. 비밀번호 일치 여부 + boolean ok = passwordEncoder.matches(loginDTO.getPassword(), member.getPassword()); + if(!ok) { + throw new LoginException( + ProcessResultCode.FAIL_LOGIN.getResultCode(), + ProcessResultCode.FAIL_LOGIN.getMessage() + ); + } + + // 5. ACCESS, REFRESH TOKEN 생성 & REFRESH TOKEN 저장 + LoginResponse loginResponse = authService.loginAfterSignup(member.getUserId(), "web-basic"); + + resultCode = loginResponse.tokens().resultCode(); + message = loginResponse.tokens().message(); + + dataMap = Map.of( + "accessToken", loginResponse.tokens().accessToken(), + "accessTokenExpiresIn", loginResponse.tokens().accessTokenExpiresIn(), + "userId", member.getUserId(), + "membershipNo", loginResponse.membershipNo(), + "refreshToken", loginResponse.tokens().refreshToken(), + "refreshTokenExpiresIn", loginResponse.tokens().refreshTokenExpiresIn() + ); + + return ApiResponse.resultData(dataMap, resultCode, message); + } + + public ApiResponse refreshToken(RefreshTokenRequestDTO refreshTokenRequestDTO) { + + String resultCode = ""; + String message = ""; + Map data = new HashMap<>(); + + // 1. 필수 입력값 확인 + if (inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 2. ACCESS, REFRESH TOKEN 재생성 + TokenPair pair = authService.rotate(refreshTokenRequestDTO.getRefreshToken()); + + resultCode = pair.resultCode(); + message = pair.message(); + + if(!resultCode.equals("200")) { + throw new LoginException(resultCode, message); + } + + data.put("accessToken", pair.accessToken()); + data.put("accessTokenExpiresIn", pair.accessTokenExpiresIn()); + data.put("refreshToken", pair.refreshToken()); + data.put("refreshTokenExpiresIn", pair.refreshTokenExpiresIn()); + + return ApiResponse.resultData(data, resultCode, message); + } + + public ApiResponse logout(RefreshTokenRequestDTO refreshTokenRequestDTO) { + + // 1. 필수 입력값 확인 + if(inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { + throw new LoginException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 2. 로그아웃 + authService.logout(refreshTokenRequestDTO.getRefreshToken()); // DB REVOKE + + return ApiResponse.result( + ProcessResultCode.SUCCESS_LOGOUT.getResultCode(), + ProcessResultCode.SUCCESS_LOGOUT.getMessage() + ); } public int selectMemberCnt(LoginDTO loginDTO){ @@ -28,13 +253,9 @@ public LoginVO findByUserId(LoginDTO loginDTO) { return loginMapper.findByUserId(loginDTO); } - public List myUserIdList(SearchUserIdDTO searchUserIdDTO){ - return loginMapper.myUserIdList(searchUserIdDTO); - } + public List myUserIdList(SearchUserIdDTO searchUserIdDTO){ return loginMapper.myUserIdList(searchUserIdDTO);} - public int updatePassword(LoginDTO loginDTO){ - return loginMapper.updatePassword(loginDTO); - } + public int updatePassword(LoginDTO loginDTO){return loginMapper.updatePassword(loginDTO);} public LoginVO findMembershipNo(LoginVO vo) { return loginMapper.findMembershipNo(vo); diff --git a/src/main/java/com/barogagi/response/ApiResponse.java b/src/main/java/com/barogagi/response/ApiResponse.java index f5bd553..9b773b2 100644 --- a/src/main/java/com/barogagi/response/ApiResponse.java +++ b/src/main/java/com/barogagi/response/ApiResponse.java @@ -30,4 +30,19 @@ public static ApiResponse error(String code, String message) { res.message = message; return res; } + + public static ApiResponse resultData(T data, String code, String message) { + ApiResponse res = new ApiResponse<>(); + res.resultCode = code; + res.message = message; + res.data = data; + return res; + } + + public static ApiResponse result(String code, String message) { + ApiResponse res = new ApiResponse<>(); + res.resultCode = code; + res.message = message; + return res; + } } diff --git a/src/main/java/com/barogagi/terms/controller/TermsController.java b/src/main/java/com/barogagi/terms/controller/TermsController.java index f726d43..2612f24 100644 --- a/src/main/java/com/barogagi/terms/controller/TermsController.java +++ b/src/main/java/com/barogagi/terms/controller/TermsController.java @@ -1,168 +1,44 @@ package com.barogagi.terms.controller; -import com.barogagi.member.login.dto.LoginVO; -import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; import com.barogagi.terms.dto.*; import com.barogagi.terms.service.TermsService; -import com.barogagi.util.InputValidate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; -import java.util.List; - @Tag(name = "약관", description = "약관 관련 API") @RestController @RequestMapping("/api/v1/terms") +@RequiredArgsConstructor public class TermsController { - private static final Logger logger = LoggerFactory.getLogger(TermsController.class); - private final InputValidate inputValidate; private final TermsService termsService; - private final LoginService loginService; - - private final String API_SECRET_KEY; - - @Autowired - public TermsController(Environment environment, InputValidate inputValidate, - TermsService termsService, LoginService loginService){ - this.inputValidate = inputValidate; - this.termsService = termsService; - this.loginService = loginService; - this.API_SECRET_KEY = environment.getProperty("api.secret-key"); - } @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.
회원가입 시 사용할 경우 termsType 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 조회에 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "조회하실 약관의 종류 값이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "약관이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping public ApiResponse termsList(@RequestHeader("API-KEY") String apiSecretKey, @RequestParam String termsType){ - logger.info("CALL /api/v1/terms"); - logger.info("[input] API_SECRET_KEY={}", apiSecretKey); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(apiSecretKey.equals(API_SECRET_KEY)) { - - if(inputValidate.isEmpty(termsType)) { - resultCode = "101"; - message = "조회하실 약관의 종류 값이 존재하지 않습니다."; - } else { - TermsInputDTO termsInputDTO = new TermsInputDTO(); - termsInputDTO.setTermsType(termsType); - List termsList = termsService.selectTermsList(termsInputDTO); - - int termsCnt = termsList.size(); - logger.info("termsCnt={}", termsCnt); - if(termsCnt > 0) { - resultCode = "200"; - message = "약관 조회에 성공하였습니다."; - apiResponse.setData(termsList); - - } else { - resultCode = "102"; - message = "약관이 존재하지 않습니다."; - } - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return termsService.termsListProcess(apiSecretKey, termsType); } @Operation(summary = "약관 동의 여부 저장", description = "약관 동의 여부 저장 기능입니다.", responses = { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 저장에 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "해당 사용자의 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "해당 사용자의 정보가 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "약관 저장에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/terms-agreements") public ApiResponse insertTermsAgree(@RequestBody TermsDTO termsDTO) { - logger.info("CALL /api/v1/terms/{userId}/terms-agreements"); - logger.info("[input] API_SECRET_KEY={}", termsDTO.getApiSecretKey()); - - ApiResponse apiResponse = new ApiResponse(); - String resultCode = ""; - String message = ""; - - try { - if(termsDTO.getApiSecretKey().equals(API_SECRET_KEY)) { - - String userId = termsDTO.getUserId(); - LoginVO lvo = new LoginVO(); - lvo.setUserId(userId); - - LoginVO loginVO = loginService.findMembershipNo(lvo); - if(null != loginVO) { - - List termsAgreeDTOList = new ArrayList<>(); - List termsAgreeList = termsDTO.getTermsAgreeList(); - - for(TermsProcessDTO termsProcessDTO : termsAgreeList) { - - TermsAgreeDTO termsAgreeDTO = new TermsAgreeDTO(); - termsAgreeDTO.setMembershipNo(loginVO.getMembershipNo()); - termsAgreeDTO.setTermsNum(termsProcessDTO.getTermsNum()); - termsAgreeDTO.setAgreeYn(termsProcessDTO.getAgreeYn()); - - termsAgreeDTOList.add(termsAgreeDTO); - } - String resCode = termsService.insertTermsAgreeList(termsAgreeDTOList); - if(resCode.equals("200")) { - resultCode = "200"; - message = "약관 저장에 성공하였습니다."; - } else { - resultCode = "300"; - message = "약관 저장에 실패하였습니다."; - } - - } else { - resultCode = "101"; - message = "해당 사용자의 정보가 존재하지 않습니다."; - } - - } else { - resultCode = "100"; - message = "잘못된 접근입니다."; - } - - } catch (Exception e) { - resultCode = "400"; - message = "오류가 발생하였습니다."; - throw new RuntimeException(e); - } finally { - apiResponse.setResultCode(resultCode); - apiResponse.setMessage(message); - } - - return apiResponse; + return termsService.termsAgreementsProcess(termsDTO); } } diff --git a/src/main/java/com/barogagi/terms/exception/TermsException.java b/src/main/java/com/barogagi/terms/exception/TermsException.java new file mode 100644 index 0000000..9ecb1a7 --- /dev/null +++ b/src/main/java/com/barogagi/terms/exception/TermsException.java @@ -0,0 +1,12 @@ +package com.barogagi.terms.exception; + +import com.barogagi.config.exception.BusinessException; +import lombok.Getter; + +@Getter +public class TermsException extends BusinessException { + + public TermsException(String resultCode, String message) { + super(resultCode, message); + } +} diff --git a/src/main/java/com/barogagi/terms/service/TermsService.java b/src/main/java/com/barogagi/terms/service/TermsService.java index 6e49b3d..3e16b0f 100644 --- a/src/main/java/com/barogagi/terms/service/TermsService.java +++ b/src/main/java/com/barogagi/terms/service/TermsService.java @@ -1,34 +1,145 @@ package com.barogagi.terms.service; -import com.barogagi.terms.dto.TermsAgreeDTO; +import com.barogagi.config.resultCode.ProcessResultCode; +import com.barogagi.member.login.dto.LoginVO; +import com.barogagi.member.login.service.LoginService; +import com.barogagi.response.ApiResponse; +import com.barogagi.terms.dto.*; +import com.barogagi.terms.exception.TermsException; import com.barogagi.terms.mapper.TermsMapper; -import com.barogagi.terms.dto.TermsInputDTO; -import com.barogagi.terms.dto.TermsOutputDTO; -import com.barogagi.terms.dto.TermsProcessDTO; +import com.barogagi.util.InputValidate; +import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.Validator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; +import java.util.ArrayList; import java.util.List; @Service public class TermsService { - private TermsMapper termsMapper; + private final TermsMapper termsMapper; + private final Validator validator; + private final InputValidate inputValidate; + private final LoginService loginService; @Autowired - public TermsService(TermsMapper termsMapper) { + public TermsService( + TermsMapper termsMapper, + Validator validator, + InputValidate inputValidate, + LoginService loginService + ) + { this.termsMapper = termsMapper; + this.validator = validator; + this.inputValidate = inputValidate; + this.loginService = loginService; + } + + public ApiResponse termsListProcess(String apiSecretKey, String termsType) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(apiSecretKey)) { + throw new TermsException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(termsType)) { + throw new TermsException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + // 3. 약관 조회 + TermsInputDTO termsInputDTO = new TermsInputDTO(); + termsInputDTO.setTermsType(termsType); + List termsList = this.selectTermsList(termsInputDTO); + + if(termsList.isEmpty()) { + throw new TermsException( + ProcessResultCode.NOT_FOUND_TERMS.getResultCode(), + ProcessResultCode.NOT_FOUND_TERMS.getMessage() + ); + + } + + return ApiResponse.resultData( + termsList, + ProcessResultCode.FOUND_TERMS.getResultCode(), + ProcessResultCode.FOUND_TERMS.getMessage() + ); + } + + public ApiResponse termsAgreementsProcess(TermsDTO termsDTO) { + + // 1. API SECRET KEY 일치 여부 확인 + if(!validator.apiSecretKeyCheck(termsDTO.getApiSecretKey())) { + throw new TermsException( + ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), + ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() + ); + } + + // 2. 필수 입력값 확인 + if(inputValidate.isEmpty(termsDTO.getUserId()) || + termsDTO.getTermsAgreeList() == null || + termsDTO.getTermsAgreeList().isEmpty()) { + throw new TermsException( + ProcessResultCode.EMPTY_DATA.getResultCode(), + ProcessResultCode.EMPTY_DATA.getMessage() + ); + } + + LoginVO lvo = new LoginVO(); + lvo.setUserId(termsDTO.getUserId()); + LoginVO loginVO = loginService.findMembershipNo(lvo); + if(null == loginVO) { + throw new TermsException( + ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), + ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() + ); + } + + List termsAgreeDTOList = new ArrayList<>(); + List termsAgreeList = termsDTO.getTermsAgreeList(); + + for(TermsProcessDTO termsProcessDTO : termsAgreeList) { + TermsAgreeDTO termsAgreeDTO = new TermsAgreeDTO(); + termsAgreeDTO.setMembershipNo(loginVO.getMembershipNo()); + termsAgreeDTO.setTermsNum(termsProcessDTO.getTermsNum()); + termsAgreeDTO.setAgreeYn(termsProcessDTO.getAgreeYn()); + termsAgreeDTOList.add(termsAgreeDTO); + } + String resCode = this.insertTermsAgreeList(termsAgreeDTOList); + + if(!resCode.equals("200")) { + throw new TermsException( + ProcessResultCode.FAIL_INSERT_TERMS.getResultCode(), + ProcessResultCode.FAIL_INSERT_TERMS.getMessage() + ); + } + + return ApiResponse.result( + ProcessResultCode.SUCCESS_INSERT_TERMS.getResultCode(), + ProcessResultCode.SUCCESS_INSERT_TERMS.getMessage() + ); } // 사용중인 약관 목록 조회 - public List selectTermsList(TermsInputDTO termsInputDTO) throws Exception { + public List selectTermsList(TermsInputDTO termsInputDTO) { return termsMapper.selectTermsList(termsInputDTO); } // 약관 동의 여부 저장 - public int insertTermsAgreeInfo(TermsAgreeDTO vo) throws Exception { + public int insertTermsAgreeInfo(TermsAgreeDTO vo) { return termsMapper.insertTermsAgreeInfo(vo); } diff --git a/src/main/java/com/barogagi/util/MembershipUtil.java b/src/main/java/com/barogagi/util/MembershipUtil.java index ab18258..e9b4ebf 100644 --- a/src/main/java/com/barogagi/util/MembershipUtil.java +++ b/src/main/java/com/barogagi/util/MembershipUtil.java @@ -1,5 +1,6 @@ package com.barogagi.util; +import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.exception.BasicException; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; @@ -21,12 +22,15 @@ public Map membershipNoService(HttpServletRequest request) { try { Object membershipNoAttr = request.getAttribute("membershipNo"); if(membershipNoAttr == null) { - throw new BasicException("401", "접근 권한이 존재하지 않습니다."); + throw new BasicException( + ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), + ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() + ); } membershipNo = String.valueOf(membershipNoAttr); - resultCode = "200"; - message = "회원 번호가 존재합니다."; + resultCode = ResultCode.EXIST_ACCESS_AUTH.getResultCode(); + message = ResultCode.EXIST_ACCESS_AUTH.getMessage(); } catch (BasicException ex) { resultCode = ex.getResultCode(); @@ -39,4 +43,17 @@ public Map membershipNoService(HttpServletRequest request) { return resultMap; } + + public String selectMembershipNo(HttpServletRequest request) { + + Object membershipNoAttr = request.getAttribute("membershipNo"); + if(null == membershipNoAttr) { + throw new BasicException( + ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), + ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() + ); + } + + return String.valueOf(membershipNoAttr); + } } diff --git a/src/main/java/com/barogagi/util/Validator.java b/src/main/java/com/barogagi/util/Validator.java index e81a49b..5949a3b 100644 --- a/src/main/java/com/barogagi/util/Validator.java +++ b/src/main/java/com/barogagi/util/Validator.java @@ -1,5 +1,7 @@ package com.barogagi.util; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; import java.util.regex.Pattern; @@ -10,6 +12,18 @@ public class Validator { // 금칙어 목록 예시 (확장 가능) private static final String[] BLOCKED_WORDS = {"admin", "운영자"}; + private final String API_SECRET_KEY; + + @Autowired + public Validator(Environment environment) { + this.API_SECRET_KEY = environment.getProperty("api.secret-key"); + } + + // API SECRET KEY 검증 + public boolean apiSecretKeyCheck(String apiSecretKey) { + return apiSecretKey.equals(API_SECRET_KEY); + } + // 아이디 검증 public boolean isValidId(String userId) { diff --git a/src/main/java/com/barogagi/util/exception/BasicException.java b/src/main/java/com/barogagi/util/exception/BasicException.java index 198ad27..814c567 100644 --- a/src/main/java/com/barogagi/util/exception/BasicException.java +++ b/src/main/java/com/barogagi/util/exception/BasicException.java @@ -1,23 +1,24 @@ package com.barogagi.util.exception; +import com.barogagi.config.exception.BusinessException; import lombok.Getter; @Getter -public class BasicException extends RuntimeException { +public class BasicException extends BusinessException { private final String resultCode; private final ErrorCode errorCode; // 신규 생성자 public BasicException(ErrorCode errorCode) { - super(errorCode.getMessage()); + super(errorCode.getCode(), errorCode.getMessage()); this.errorCode = errorCode; this.resultCode = errorCode.getCode(); } // 기존 생성자 public BasicException(String resultCode, String message) { - super(message); + super(resultCode, message); this.resultCode = resultCode; errorCode = null; } diff --git a/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java index e897815..db6f7ea 100644 --- a/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package com.barogagi.util.exception; +import com.barogagi.config.exception.BusinessException; import com.barogagi.response.ApiResponse; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -31,4 +32,9 @@ public ResponseEntity> handleUnknown(Exception e) { ErrorCode.INTERNAL_ERROR.getMessage() )); } + + @ExceptionHandler(BusinessException.class) + public ApiResponse handleBusinessException(BusinessException e) { + return ApiResponse.result(e.getResultCode(), e.getMessage()); + } } diff --git a/src/main/resources/mapper/JoinMapper.xml b/src/main/resources/mapper/JoinMapper.xml index 8acef38..4b73d5e 100644 --- a/src/main/resources/mapper/JoinMapper.xml +++ b/src/main/resources/mapper/JoinMapper.xml @@ -8,7 +8,7 @@ ]]> - - Date: Mon, 29 Dec 2025 19:29:50 +0900 Subject: [PATCH 57/79] =?UTF-8?q?[FIX]=20:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=ED=98=95=EC=8B=9D=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../approval/exception/ApprovalException.java | 5 +- .../approval/service/ApprovalService.java | 49 ++------ .../com/barogagi/config/JwtAuthFilter.java | 21 +--- .../com/barogagi/config/SecurityConfig.java | 6 +- .../config/exception/BusinessException.java | 14 ++- .../config/resultCode/ProcessResultCode.java | 82 ------------- .../config/resultCode/ResultCode.java | 23 ---- .../logging/ServiceLoggingAspect.java | 2 +- .../mainPage/exception/MainPageException.java | 6 +- .../mainPage/service/MainPageService.java | 46 +++----- .../basic/join/controller/JoinController.java | 10 +- .../basic/join/exception/JoinException.java | 5 +- .../basic/join/service/BasicJoinService.java | 111 ++++-------------- .../info/exception/MemberInfoException.java | 5 +- .../member/info/service/MemberService.java | 39 ++---- .../InvalidRefreshTokenException.java | 5 +- .../login/exception/LoginException.java | 5 +- .../member/login/service/AuthService.java | 37 +++--- .../member/login/service/LoginService.java | 82 ++++--------- .../region/controller/RegionController.java | 2 +- .../com/barogagi/response/ApiResponse.java | 19 ++- .../controller/ScheduleController.java | 26 +--- .../terms/exception/TermsException.java | 5 +- .../barogagi/terms/service/TermsService.java | 46 ++------ .../com/barogagi/util/MembershipUtil.java | 18 +-- .../util/exception/BasicException.java | 11 +- .../barogagi/util/exception/ErrorCode.java | 80 ++++++++++++- .../exception/GlobalExceptionHandler.java | 2 +- 28 files changed, 265 insertions(+), 497 deletions(-) delete mode 100644 src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java delete mode 100644 src/main/java/com/barogagi/config/resultCode/ResultCode.java diff --git a/src/main/java/com/barogagi/approval/exception/ApprovalException.java b/src/main/java/com/barogagi/approval/exception/ApprovalException.java index 5dde8e6..62b8ca6 100644 --- a/src/main/java/com/barogagi/approval/exception/ApprovalException.java +++ b/src/main/java/com/barogagi/approval/exception/ApprovalException.java @@ -1,12 +1,13 @@ package com.barogagi.approval.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class ApprovalException extends BusinessException { - public ApprovalException(String resultCode, String message) { - super(resultCode, message); + public ApprovalException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/approval/service/ApprovalService.java b/src/main/java/com/barogagi/approval/service/ApprovalService.java index 6cb6f64..feb3ed6 100644 --- a/src/main/java/com/barogagi/approval/service/ApprovalService.java +++ b/src/main/java/com/barogagi/approval/service/ApprovalService.java @@ -5,17 +5,15 @@ import com.barogagi.approval.vo.ApprovalCompleteVO; import com.barogagi.approval.vo.ApprovalSendVO; import com.barogagi.approval.vo.ApprovalVO; -import com.barogagi.config.resultCode.ProcessResultCode; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.response.ApiResponse; import com.barogagi.sendSms.dto.SendSmsVO; import com.barogagi.sendSms.service.SendSmsService; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.web.bind.annotation.RequestBody; @Service public class ApprovalService { @@ -49,18 +47,12 @@ public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(approvalSendVO.getApiSecretKey())) { - throw new ApprovalException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new ApprovalException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(approvalSendVO.getTel())) { - throw new ApprovalException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new ApprovalException(ErrorCode.EMPTY_DATA); } // 3. 처리 @@ -85,10 +77,7 @@ public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { boolean sendMessageResult = sendSmsService.sendSms(sendSmsVO); if(!sendMessageResult) { - throw new ApprovalException( - ProcessResultCode.FAIL_SEND_SMS.getResultCode(), - ProcessResultCode.FAIL_SEND_SMS.getMessage() - ); + throw new ApprovalException(ErrorCode.FAIL_SEND_SMS); } // 인증번호 암호화 @@ -98,35 +87,23 @@ public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { int insertResult = this.insertApprovalRecord(approvalVO); if(insertResult <= 0) { - throw new ApprovalException( - ProcessResultCode.ERROR_SEND_SMS.getResultCode(), - ProcessResultCode.ERROR_SEND_SMS.getMessage() - ); + throw new ApprovalException(ErrorCode.ERROR_SEND_SMS); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_SEND_SMS.getResultCode(), - ProcessResultCode.SUCCESS_SEND_SMS.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_SEND_SMS); } public ApiResponse approvalTelCheck(ApprovalCompleteVO approvalCompleteVO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(approvalCompleteVO.getApiSecretKey())) { - throw new ApprovalException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new ApprovalException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode()) || inputValidate.isEmpty(approvalCompleteVO.getTel())) { - throw new ApprovalException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new ApprovalException(ErrorCode.EMPTY_DATA); } // 3. 전화번호 암호화 @@ -139,16 +116,10 @@ public ApiResponse approvalTelCheck(ApprovalCompleteVO approvalCompleteVO) { // 4. 인증 int updateResult = this.updateApprovalComplete(approvalVO); if(updateResult != 1){ - throw new ApprovalException( - ProcessResultCode.FAIL_CHECK_SMS.getResultCode(), - ProcessResultCode.FAIL_CHECK_SMS.getMessage() - ); + throw new ApprovalException(ErrorCode.FAIL_CHECK_SMS); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_CHECK_SMS.getResultCode(), - ProcessResultCode.SUCCESS_CHECK_SMS.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_CHECK_SMS); } public int updateApprovalRecord(ApprovalVO vo){ diff --git a/src/main/java/com/barogagi/config/JwtAuthFilter.java b/src/main/java/com/barogagi/config/JwtAuthFilter.java index 40c4f70..233b7df 100644 --- a/src/main/java/com/barogagi/config/JwtAuthFilter.java +++ b/src/main/java/com/barogagi/config/JwtAuthFilter.java @@ -4,7 +4,7 @@ import com.barogagi.member.info.service.MemberService; import com.barogagi.member.login.exception.InvalidRefreshTokenException; import com.barogagi.util.JwtUtil; -import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.exception.ErrorCode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.*; @@ -53,21 +53,12 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, chain.doFilter(req, res); } catch (ExpiredJwtException e) { // 유효기간이 지나서 만료된 경우 - writeErrorResponse( - ResultCode.EXPIRE_TOKEN.getResultCode(), - ResultCode.EXPIRE_TOKEN.getMessage() - ); + writeErrorResponse(ErrorCode.EXPIRE_TOKEN); } catch (JwtException | SecurityException e) { // 위조되었거나 변조되었거나 구조가 잘못되었을 경우 - writeErrorResponse( - ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), - ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() - ); + writeErrorResponse(ErrorCode.NOT_EXIST_ACCESS_AUTH); } catch (Exception e) { - writeErrorResponse( - ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), - ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() - ); + writeErrorResponse(ErrorCode.NOT_EXIST_ACCESS_AUTH); } } @@ -78,8 +69,8 @@ protected boolean shouldNotFilter(HttpServletRequest request) { return p.startsWith("/auth/") || p.startsWith("/login/basic/membership/userId/search"); } - private void writeErrorResponse(String resultCode, String message) throws IOException { - throw new InvalidRefreshTokenException(resultCode, message); + private void writeErrorResponse(ErrorCode errorCode) throws IOException { + throw new InvalidRefreshTokenException(errorCode); } } diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index b8952a7..0be7381 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -2,7 +2,7 @@ import com.barogagi.member.oauth.join.service.CustomOidcUserService; import com.barogagi.member.oauth.join.service.DelegatingOAuth2UserService; -import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.exception.ErrorCode; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,8 +77,8 @@ SecurityFilterChain filterChain( // 브라우저 리다이렉트 대신 401 JSON .exceptionHandling(ex -> ex.authenticationEntryPoint((req, res, e) -> { - String resultCode = ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(); - String message = ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage(); + String resultCode = ErrorCode.NOT_EXIST_ACCESS_AUTH.getCode(); + String message = ErrorCode.NOT_EXIST_ACCESS_AUTH.getMessage(); res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.setContentType("application/json;charset=UTF-8"); diff --git a/src/main/java/com/barogagi/config/exception/BusinessException.java b/src/main/java/com/barogagi/config/exception/BusinessException.java index a87dcdf..0ef97df 100644 --- a/src/main/java/com/barogagi/config/exception/BusinessException.java +++ b/src/main/java/com/barogagi/config/exception/BusinessException.java @@ -1,14 +1,20 @@ package com.barogagi.config.exception; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; +import org.springframework.http.HttpStatus; @Getter public abstract class BusinessException extends RuntimeException { - private final String resultCode; + private final HttpStatus httpStatus; + private final String code; + private final String message; - public BusinessException(String resultCode, String message) { - super(message); - this.resultCode = resultCode; + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.httpStatus = errorCode.getStatus(); + this.code = errorCode.getCode(); + this.message = errorCode.getMessage(); } } diff --git a/src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java b/src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java deleted file mode 100644 index a2d9f18..0000000 --- a/src/main/java/com/barogagi/config/resultCode/ProcessResultCode.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.barogagi.config.resultCode; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum ProcessResultCode { - - EMPTY_DATA("101", "정보를 입력해주세요."), - - // nickname - INVALID_NICKNAME("102", "적합하지 않는 닉네임입니다."), - UNAVAILABLE_NICKNAME("103", "해당 닉네임 사용이 불가능합니다."), - AVAILABLE_NICKNAME("200", "사용 가능한 닉네임입니다."), - - // userId - INVALID_USER_ID("102", "적합한 아이디가 아닙니다."), - UNAVAILABLE_USER_ID("300", "해당 아이디 사용이 불가능합니다."), - AVAILABLE_USER_ID("200", "해당 아이디 사용이 가능합니다."), - - // signUp - INVALID_SIGN_UP("102", "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), - SUCCESS_SIGN_UP("200", "회원가입에 성공하였습니다."), - FAIL_SIGN_UP("300", "회원가입에 실패하였습니다."), - - // deleteAccount - SUCCESS_DELETE_ACCOUNT("200", "회원 탈퇴되었습니다."), - FAIL_DELETE_ACCOUNT("300", "회원 탈퇴 실패하였습니다."), - - // findUser - FOUND_ACCOUNT("200", "해당 전화번호로 가입된 아이디가 존재합니다."), - NOT_FOUND_ACCOUNT("201", "해당 전화번호로 가입된 계정이 존재하지 않습니다."), - - // updatePassword - SUCCESS_UPDATE_PASSWORD("200", "비밀번호 재설정에 성공하였습니다."), - FAIL_UPDATE_PASSWORD("300", "비밀번호 재설정에 실패하였습니다."), - - // Login - NOT_FOUND_USER_INFO("102", "회원 정보가 존재하지 않습니다."), - FAIL_LOGIN("103", "로그인에 실패하였습니다."), - SUCCESS_LOGIN("200", "로그인에 성공하였습니다."), - - // refreshToken - REQUIRED_LOGIN("110", "로그인을 진행해주세요."), - REQUIRED_RE_LOGIN("120", "로그인을 다시 진행해주세요."), - SUCCESS_REFRESH_TOKEN("200", "토큰이 발급되었습니다."), - FAIL_REFRESH_TOKEN("130", "토큰 발급에 실패하였습니다."), - - // logout - FAIL_LOGOUT("300", "로그아웃 실패하였습니다."), - SUCCESS_LOGOUT("200", "로그아웃 되었습니다."), - - // terms - FOUND_TERMS("200", "약관 조회에 성공하였습니다."), - NOT_FOUND_TERMS("102", "약관이 존재하지 않습니다."), - SUCCESS_INSERT_TERMS("200", "약관 저장에 성공하였습니다."), - FAIL_INSERT_TERMS("300", "약관 저장에 실패하였습니다."), - - // memberInfo - FOUND_USER_INFO("200", "회원 정보 조회가 완료되었습니다."), - FAIL_UPDATE_USER_INFO("404", "사용자 정보 수정 실패하였습니다."), - SUCCESS_UPDATE_USER_INFO("200", "사용자 정보 수정 완료하였습니다."), - - // mainPage - NOT_FOUND_SCHEDULE("201", "일정이 존재하지 않습니다."), - FOUND_SCHEDULE("200", "조회 성공하였습니다."), - NOT_FOUND_POPULAR_TAG("201", "인기 태그 목록이 존재하지 않습니다."), - FOUND_POPULAR_TAG("200", "인기 태그 조회 완료하였습니다."), - NOT_FOUND_POPULAR_REGION("201", "인기 지역 목록이 존재하지 않습니다."), - FOUND_POPULAR_REGION("200", "인기 지역 조회 완료하였습니다."), - - // approval - SUCCESS_SEND_SMS("200", "인증번호 발송에 성공하었습니다."), - FAIL_SEND_SMS("103", "인증번호 발송에 실패하였습니다."), - ERROR_SEND_SMS("102", "오류가 발생하였습니다."), - SUCCESS_CHECK_SMS("200", "인증이 완료되었습니다."), - FAIL_CHECK_SMS("300", "인증에 실패하였습니다."); - - private final String resultCode; - private final String message; -} diff --git a/src/main/java/com/barogagi/config/resultCode/ResultCode.java b/src/main/java/com/barogagi/config/resultCode/ResultCode.java deleted file mode 100644 index a854ce5..0000000 --- a/src/main/java/com/barogagi/config/resultCode/ResultCode.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.barogagi.config.resultCode; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum ResultCode { - - // API_SECRET_KEY 일치 X - NOT_EQUAL_API_SECRET_KEY("100", "잘못된 접근입니다."), - - // ACCESS TOKEN - NOT_EXIST_ACCESS_AUTH("401", "접근 권한이 존재하지 않습니다."), - EXIST_ACCESS_AUTH("200", "회원 번호가 존재합니다."), - EXPIRE_TOKEN("300", "Token이 만료되었습니다."), - - // 서버 오류 - ERROR("400","오류가 발생하였습니다."); - - private final String resultCode; - private final String message; -} diff --git a/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java b/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java index 0e43bf7..d32a1da 100644 --- a/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java +++ b/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java @@ -32,7 +32,7 @@ public Object logService(ProceedingJoinPoint joinPoint) throws Throwable { return result; } catch (BusinessException ex) { - log.error("Service BusinessException - {}.{} / resultCode - {} / message - {}", className, methodName, ex.getResultCode(), ex.getMessage(), ex); + log.error("Service BusinessException - {}.{} / httpStatus - {} / errorCode - {} / message - {}", className, methodName, ex.getHttpStatus(), ex.getCode(), ex.getMessage(), ex); throw ex; } catch (Exception e) { diff --git a/src/main/java/com/barogagi/mainPage/exception/MainPageException.java b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java index 11e04cf..c08b15e 100644 --- a/src/main/java/com/barogagi/mainPage/exception/MainPageException.java +++ b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java @@ -1,12 +1,12 @@ package com.barogagi.mainPage.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class MainPageException extends BusinessException { - - public MainPageException(String resultCode, String message) { - super(resultCode, message); + public MainPageException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/mainPage/service/MainPageService.java b/src/main/java/com/barogagi/mainPage/service/MainPageService.java index 3f766c4..7358c32 100644 --- a/src/main/java/com/barogagi/mainPage/service/MainPageService.java +++ b/src/main/java/com/barogagi/mainPage/service/MainPageService.java @@ -4,11 +4,10 @@ import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.mainPage.mapper.MainPageMapper; import com.barogagi.mainPage.response.MainPageResponse; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.response.ApiResponse; import com.barogagi.util.MembershipUtil; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,11 +44,14 @@ public MainPageResponse selectUserScheduleInfoProcess(HttpServletRequest request String message = ""; List tagList = null; RegionInfoDTO regionInfo = null; + UserInfoResponseDTO userInfoResponseDTO = null; // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MainPageException( + + return MainPageResponse.resultData( + userInfoResponseDTO, tagList, regionInfo, String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message")) ); @@ -60,15 +62,15 @@ public MainPageResponse selectUserScheduleInfoProcess(HttpServletRequest request // 2. 유저 일정 정보 조회 UserInfoRequestDTO userInfoRequestDTO = new UserInfoRequestDTO(); userInfoRequestDTO.setMembershipNo(membershipNo); - UserInfoResponseDTO userInfoResponseDTO = this.selectUserScheduleInfo(userInfoRequestDTO); + userInfoResponseDTO = this.selectUserScheduleInfo(userInfoRequestDTO); if(null == userInfoResponseDTO) { - resultCode = ProcessResultCode.NOT_FOUND_SCHEDULE.getResultCode(); - message = ProcessResultCode.NOT_FOUND_SCHEDULE.getMessage(); + resultCode = ErrorCode.NOT_FOUND_SCHEDULE.getCode(); + message = ErrorCode.NOT_FOUND_SCHEDULE.getMessage(); } else { - resultCode = ProcessResultCode.FOUND_SCHEDULE.getResultCode(); - message = ProcessResultCode.FOUND_SCHEDULE.getMessage(); + resultCode = ErrorCode.FOUND_SCHEDULE.getCode(); + message = ErrorCode.FOUND_SCHEDULE.getMessage(); // 3. 해당 schedule에 대한 태그 목록 조회 userInfoRequestDTO.setScheduleNum(userInfoResponseDTO.getScheduleNum()); @@ -86,26 +88,20 @@ public ApiResponse selectPopularTagList(String apiSecretKey) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new MainPageException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new MainPageException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 인기 태그 조회 List tagRankInfoList = this.selectTagRankList(); if(tagRankInfoList.isEmpty()) { - throw new MainPageException( - ProcessResultCode.NOT_FOUND_POPULAR_TAG.getResultCode(), - ProcessResultCode.NOT_FOUND_POPULAR_TAG.getMessage() - ); + throw new MainPageException(ErrorCode.NOT_FOUND_POPULAR_TAG); } return ApiResponse.resultData( tagRankInfoList, - ProcessResultCode.FOUND_POPULAR_TAG.getResultCode(), - ProcessResultCode.FOUND_POPULAR_TAG.getMessage() + ErrorCode.FOUND_POPULAR_TAG.getCode(), + ErrorCode.FOUND_POPULAR_TAG.getMessage() ); } @@ -113,26 +109,20 @@ public ApiResponse selectPopularRegionList(String apiSecretKey) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new MainPageException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new MainPageException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 인기 지역 조회 List regionRankInfoList = this.selectRegionRankList(); if(regionRankInfoList.isEmpty()) { - throw new MainPageException( - ProcessResultCode.NOT_FOUND_POPULAR_REGION.getResultCode(), - ProcessResultCode.NOT_FOUND_POPULAR_REGION.getMessage() - ); + throw new MainPageException(ErrorCode.NOT_FOUND_POPULAR_REGION); } return ApiResponse.resultData( regionRankInfoList, - ProcessResultCode.FOUND_POPULAR_REGION.getResultCode(), - ProcessResultCode.FOUND_POPULAR_REGION.getMessage() + ErrorCode.FOUND_POPULAR_REGION.getCode(), + ErrorCode.FOUND_POPULAR_REGION.getMessage() ); } diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index 7641ee8..1a2e02f 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -23,11 +23,11 @@ public JoinController(BasicJoinService basicJoinService) { @Operation(summary = "아이디 중복 체크 기능", description = "아이디 중복 체크 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 아이디 사용이 가능합니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디가 아닙니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "해당 아이디 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U200", description = "해당 아이디 사용이 가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U102", description = "적합한 아이디가 아닙니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U300", description = "해당 아이디 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/userid/exists") diff --git a/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java b/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java index 6c1b42f..a82a81c 100644 --- a/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java +++ b/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java @@ -1,12 +1,13 @@ package com.barogagi.member.basic.join.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class JoinException extends BusinessException { - public JoinException(String resultCode, String message) { - super(resultCode, message); + public JoinException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java b/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java index 2ef28de..60b2a03 100644 --- a/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java +++ b/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java @@ -1,20 +1,18 @@ package com.barogagi.member.basic.join.service; import com.barogagi.config.PasswordConfig; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.basic.join.dto.JoinDTO; import com.barogagi.member.basic.join.dto.JoinRequestDTO; import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.exception.JoinException; import com.barogagi.member.login.dto.RefreshTokenRequestDTO; -import com.barogagi.member.login.exception.InvalidRefreshTokenException; import com.barogagi.member.login.service.AccountService; import com.barogagi.member.login.service.AuthService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -54,26 +52,17 @@ public ApiResponse checkNickname(String apiSecretKey, String nickname) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new JoinException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new JoinException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(nickname)) { - throw new JoinException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new JoinException(ErrorCode.EMPTY_DATA); } // 3. 적합한 닉네임인지 확인 if(!validator.isValidNickname(nickname)) { - throw new JoinException( - ProcessResultCode.INVALID_NICKNAME.getResultCode(), - ProcessResultCode.INVALID_NICKNAME.getMessage() - ); + throw new JoinException(ErrorCode.INVALID_NICKNAME); } // 4. 닉네임 중복 체크 @@ -82,16 +71,10 @@ public ApiResponse checkNickname(String apiSecretKey, String nickname) { int nickNameCnt = joinService.selectNicknameCnt(nickNameDTO); if(nickNameCnt > 0) { - throw new JoinException( - ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), - ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() - ); + throw new JoinException(ErrorCode.UNAVAILABLE_NICKNAME); } - return ApiResponse.result( - ProcessResultCode.AVAILABLE_NICKNAME.getResultCode(), - ProcessResultCode.AVAILABLE_NICKNAME.getMessage() - ); + return ApiResponse.result(ErrorCode.AVAILABLE_NICKNAME); } // 아이디 중복 체크 service @@ -99,26 +82,17 @@ public ApiResponse checkUserId(String apiSecretKey, String userId) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new JoinException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new JoinException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(userId)) { - throw new JoinException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new JoinException(ErrorCode.EMPTY_DATA); } // 3. 적합한 아이디인지 확인 if(!validator.isValidId(userId)) { - throw new JoinException( - ProcessResultCode.INVALID_USER_ID.getResultCode(), - ProcessResultCode.INVALID_USER_ID.getMessage() - ); + throw new JoinException(ErrorCode.INVALID_USER_ID); } // 4. 아이디 중복 체크 @@ -128,26 +102,17 @@ public ApiResponse checkUserId(String apiSecretKey, String userId) { int checkUserId = joinService.selectUserIdCnt(joinDTO); if(checkUserId > 0) { - throw new JoinException( - ProcessResultCode.UNAVAILABLE_USER_ID.getResultCode(), - ProcessResultCode.UNAVAILABLE_USER_ID.getMessage() - ); + throw new JoinException(ErrorCode.UNAVAILABLE_USER_ID); } - return ApiResponse.result( - ProcessResultCode.AVAILABLE_USER_ID.getResultCode(), - ProcessResultCode.AVAILABLE_USER_ID.getMessage() - ); + return ApiResponse.result(ErrorCode.AVAILABLE_USER_ID); } public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(joinRequestDTO.getApiSecretKey())) { - throw new JoinException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new JoinException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 @@ -157,10 +122,7 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { || inputValidate.isEmpty(joinRequestDTO.getPassword()) || inputValidate.isEmpty(joinRequestDTO.getTel())) { - throw new JoinException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new JoinException(ErrorCode.EMPTY_DATA); } // 3. 적합한 아이디인지 확인 @@ -169,10 +131,7 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { validator.isValidId(joinRequestDTO.getUserId()) && validator.isValidPassword(joinRequestDTO.getPassword())) ) { - throw new JoinException( - ProcessResultCode.INVALID_SIGN_UP.getResultCode(), - ProcessResultCode.INVALID_SIGN_UP.getMessage() - ); + throw new JoinException(ErrorCode.INVALID_SIGN_UP); } // 4. 암호화 @@ -205,10 +164,7 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { // 5. 아이디 중복 검증 if(duplicateUserId > 0) { - throw new JoinException( - ProcessResultCode.UNAVAILABLE_USER_ID.getResultCode(), - ProcessResultCode.UNAVAILABLE_USER_ID.getMessage() - ); + throw new JoinException(ErrorCode.UNAVAILABLE_USER_ID); } // 닉네임 값이 넘어올 경우 중복 검사 @@ -216,10 +172,7 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { // 닉네임 적합성 검사 if(!validator.isValidNickname(joinRequestDTO.getNickName())) { - throw new JoinException( - ProcessResultCode.INVALID_NICKNAME.getResultCode(), - ProcessResultCode.INVALID_NICKNAME.getMessage() - ); + throw new JoinException(ErrorCode.INVALID_NICKNAME); } NickNameDTO nickNameDTO = new NickNameDTO(); @@ -227,41 +180,31 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { int selectNicknameCnt = joinService.selectNicknameCnt(nickNameDTO); if(selectNicknameCnt > 0) { - throw new JoinException( - ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), - ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() - ); + throw new JoinException(ErrorCode.UNAVAILABLE_NICKNAME); } } // 6. 회원 정보 저장 int insertResult = joinService.insertMembershipInfo(joinDTO); if(insertResult <= 0){ - throw new JoinException( - ProcessResultCode.FAIL_SIGN_UP.getResultCode(), - ProcessResultCode.FAIL_SIGN_UP.getMessage() - ); + throw new JoinException(ErrorCode.FAIL_SIGN_UP); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_SIGN_UP.getResultCode(), - ProcessResultCode.SUCCESS_SIGN_UP.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_SIGN_UP); } public ApiResponse deleteAccount(RefreshTokenRequestDTO refreshTokenRequestDTO) { // 1. refresh token이 공백 또는 null인지 확인 if(inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { - throw new JoinException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage()); + throw new JoinException(ErrorCode.EMPTY_DATA); } // 2. refresh token을 이용해서 membershipNo 구하기 Map resultMap = authService.selectUserInfoByToken(refreshTokenRequestDTO.getRefreshToken()); if(!resultMap.get("resultCode").equals("200")) { - throw new InvalidRefreshTokenException( + + return ApiResponse.error( resultMap.get("resultCode"), resultMap.get("message") ); @@ -269,16 +212,10 @@ public ApiResponse deleteAccount(RefreshTokenRequestDTO refreshTokenRequestDTO) int deleteResult = accountService.deleteMyAccount(resultMap.get("membershipNo")); if(deleteResult <= 0) { - throw new JoinException( - ProcessResultCode.FAIL_DELETE_ACCOUNT.getResultCode(), - ProcessResultCode.FAIL_DELETE_ACCOUNT.getMessage() - ); + throw new JoinException(ErrorCode.FAIL_DELETE_ACCOUNT); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_DELETE_ACCOUNT.getResultCode(), - ProcessResultCode.SUCCESS_DELETE_ACCOUNT.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_DELETE_ACCOUNT); } } diff --git a/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java index 8888ead..952c4d3 100644 --- a/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java +++ b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java @@ -1,12 +1,13 @@ package com.barogagi.member.info.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class MemberInfoException extends BusinessException { - public MemberInfoException(String resultCode, String message) { - super(resultCode, message); + public MemberInfoException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/member/info/service/MemberService.java b/src/main/java/com/barogagi/member/info/service/MemberService.java index 1efbe21..88845ee 100644 --- a/src/main/java/com/barogagi/member/info/service/MemberService.java +++ b/src/main/java/com/barogagi/member/info/service/MemberService.java @@ -1,6 +1,5 @@ package com.barogagi.member.info.service; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.info.dto.Member; @@ -11,7 +10,7 @@ import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; import com.barogagi.util.MembershipUtil; -import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.exception.ErrorCode; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -46,7 +45,7 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MemberInfoException( + return ApiResponse.error( String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message")) ); @@ -56,10 +55,7 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { // 2. 회원 정보 조회 Member memberInfo = this.findByMembershipNo(membershipNo); if(null == memberInfo) { - throw new MemberInfoException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - ); + throw new MemberInfoException(ErrorCode.NOT_FOUND_USER_INFO); } // 이메일 복호화 @@ -73,8 +69,8 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { return ApiResponse.resultData( memberInfo, - ProcessResultCode.FOUND_USER_INFO.getResultCode(), - ProcessResultCode.FOUND_USER_INFO.getMessage() + ErrorCode.FOUND_USER_INFO.getCode(), + ErrorCode.FOUND_USER_INFO.getMessage() ); } @@ -83,9 +79,10 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MemberInfoException( + return ApiResponse.error( String.valueOf(membershipNoInfo.get("resultCode")), - String.valueOf(membershipNoInfo.get("message"))); + String.valueOf(membershipNoInfo.get("message")) + ); } String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); @@ -93,10 +90,7 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest // 2. 회원 정보 조회 Member memberInfo = this.findByMembershipNo(membershipNo); if(null == memberInfo) { - throw new MemberInfoException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - ); + throw new MemberInfoException(ErrorCode.NOT_FOUND_USER_INFO); } // 3. 데이터 처리 @@ -117,10 +111,7 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest int nickNameCnt = joinService.selectNicknameCnt(nickNameRequest); if(nickNameCnt > 0) { - throw new MemberInfoException( - ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), - ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() - ); + throw new MemberInfoException(ErrorCode.UNAVAILABLE_NICKNAME); } memberInfo.setNickName(memberRequestDTO.getNickName()); @@ -128,16 +119,10 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest int updateMemberInfo = this.updateMemberInfo(memberInfo); if(updateMemberInfo <= 0) { - throw new MemberInfoException( - ProcessResultCode.FAIL_UPDATE_USER_INFO.getResultCode(), - ProcessResultCode.FAIL_UPDATE_USER_INFO.getMessage() - ); + throw new MemberInfoException(ErrorCode.FAIL_UPDATE_USER_INFO); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_UPDATE_USER_INFO.getResultCode(), - ProcessResultCode.SUCCESS_UPDATE_USER_INFO.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_UPDATE_USER_INFO); } public Member findByMembershipNo(String membershipNo) { diff --git a/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java b/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java index 2c89470..5c4c491 100644 --- a/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java +++ b/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java @@ -1,12 +1,13 @@ package com.barogagi.member.login.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class InvalidRefreshTokenException extends BusinessException { - public InvalidRefreshTokenException(String resultCode, String message) { - super(resultCode, message); + public InvalidRefreshTokenException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/member/login/exception/LoginException.java b/src/main/java/com/barogagi/member/login/exception/LoginException.java index 84a44f9..ca5c843 100644 --- a/src/main/java/com/barogagi/member/login/exception/LoginException.java +++ b/src/main/java/com/barogagi/member/login/exception/LoginException.java @@ -1,12 +1,13 @@ package com.barogagi.member.login.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class LoginException extends BusinessException { - public LoginException(String resultCode, String message) { - super(resultCode, message); + public LoginException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/member/login/service/AuthService.java b/src/main/java/com/barogagi/member/login/service/AuthService.java index 148df4b..8e733e8 100644 --- a/src/main/java/com/barogagi/member/login/service/AuthService.java +++ b/src/main/java/com/barogagi/member/login/service/AuthService.java @@ -1,6 +1,5 @@ package com.barogagi.member.login.service; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.login.dto.*; import com.barogagi.member.login.entity.RefreshToken; import com.barogagi.member.login.entity.UserMembership; @@ -9,6 +8,7 @@ import com.barogagi.member.login.repository.RefreshTokenRepository; import com.barogagi.member.login.repository.UserMembershipRepository; import com.barogagi.util.JwtUtil; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; @@ -82,8 +82,8 @@ public LoginResponse login(LoginRequest req) { accessExp, refresh, refreshExp, - ProcessResultCode.SUCCESS_LOGIN.getResultCode(), - ProcessResultCode.SUCCESS_LOGIN.getMessage() + ErrorCode.SUCCESS_LOGIN.getCode(), + ErrorCode.SUCCESS_LOGIN.getMessage() ), no, u.getUserId(), u.getJoinType() ); @@ -114,8 +114,8 @@ public LoginResponse loginAfterSignup(String userId, String deviceId) { accessExp, refresh, refreshExp, - ProcessResultCode.SUCCESS_REFRESH_TOKEN.getResultCode(), - ProcessResultCode.SUCCESS_REFRESH_TOKEN.getMessage() + ErrorCode.SUCCESS_REFRESH_TOKEN.getCode(), + ErrorCode.SUCCESS_REFRESH_TOKEN.getMessage() ), no, u.getUserId(), u.getJoinType() ); @@ -147,19 +147,13 @@ public TokenPair rotate(String refreshToken) { // 현재 리프레시가 DB에 VALID로 존재하는지 확인 RefreshToken current = refreshRepo.findByTokenAndStatus(refreshToken, "VALID") - .orElseThrow(() -> new InvalidRefreshTokenException( - ProcessResultCode.REQUIRED_LOGIN.getResultCode(), - ProcessResultCode.REQUIRED_LOGIN.getMessage() - )); + .orElseThrow(() -> new InvalidRefreshTokenException(ErrorCode.REQUIRED_LOGIN)); // 만료 체크 if (current.getExpiresAt().isBefore(LocalDateTime.now())) { current.setStatus("REVOKED"); refreshRepo.save(current); - throw new InvalidRefreshTokenException( - ProcessResultCode.REQUIRED_RE_LOGIN.getResultCode(), - ProcessResultCode.REQUIRED_RE_LOGIN.getMessage() - ); + throw new InvalidRefreshTokenException(ErrorCode.REQUIRED_RE_LOGIN); } // 같은 멤버/디바이스의 기존 VALID 토큰들 모두 REVOKE (동시 세션 차단용) @@ -171,10 +165,7 @@ public TokenPair rotate(String refreshToken) { // 새 토큰 발급 UserMembership user = userRepo.findById(membershipNo) - .orElseThrow(() -> new InvalidRefreshTokenException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - )); + .orElseThrow(() -> new InvalidRefreshTokenException(ErrorCode.NOT_FOUND_USER_INFO)); newAccess = jwt.generateAccessToken(membershipNo, user.getUserId()); newRefresh = jwt.generateRefreshToken(membershipNo, deviceId); @@ -194,7 +185,7 @@ public TokenPair rotate(String refreshToken) { 0, "", 0, - ex.getResultCode(), + ex.getCode(), ex.getMessage() ); } @@ -202,8 +193,8 @@ public TokenPair rotate(String refreshToken) { return new TokenPair( newAccess, jwt.getAccessExpSeconds(), newRefresh, jwt.getRefreshExpSeconds(), - ProcessResultCode.SUCCESS_REFRESH_TOKEN.getResultCode(), - ProcessResultCode.SUCCESS_REFRESH_TOKEN.getMessage() + ErrorCode.SUCCESS_REFRESH_TOKEN.getCode(), + ErrorCode.SUCCESS_REFRESH_TOKEN.getMessage() ); } @@ -242,7 +233,7 @@ public Map selectUserInfoByToken(String refreshToken) { try { // 1. JWT 토큰 유효성 검증 if(!jwt.isTokenValid(refreshToken) || !jwt.isRefreshToken(refreshToken)) { - throw new InvalidRefreshTokenException("301", "유효하지 않은 refresh token입니다."); + throw new InvalidRefreshTokenException(ErrorCode.UNAVAILABLE_REFRESH_TOKEN); } // 2. membershipNo 구하기 @@ -250,7 +241,7 @@ public Map selectUserInfoByToken(String refreshToken) { // 3. membershipNo 조회가 되지 않을 경우 if(membershipNo == null || membershipNo.isBlank()) { - throw new InvalidRefreshTokenException("302", "유효한 token 정보를 찾을 수 없습니다."); + throw new InvalidRefreshTokenException(ErrorCode.NOT_FOUND_AVAILABLE_REFRESH_TOKEN); } resultCode = "200"; @@ -258,7 +249,7 @@ public Map selectUserInfoByToken(String refreshToken) { returnMap.put("membershipNo", membershipNo); } catch (InvalidRefreshTokenException e) { - resultCode = e.getResultCode(); + resultCode = e.getCode(); message = e.getMessage(); } finally { returnMap.put("resultCode", resultCode); diff --git a/src/main/java/com/barogagi/member/login/service/LoginService.java b/src/main/java/com/barogagi/member/login/service/LoginService.java index 8300cc5..7dd60fe 100644 --- a/src/main/java/com/barogagi/member/login/service/LoginService.java +++ b/src/main/java/com/barogagi/member/login/service/LoginService.java @@ -1,7 +1,6 @@ package com.barogagi.member.login.service; import com.barogagi.config.PasswordConfig; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.service.MemberService; import com.barogagi.member.login.dto.*; @@ -10,8 +9,8 @@ import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -62,29 +61,23 @@ public ApiResponse findUser(SearchUserIdDTO searchUserIdDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(searchUserIdDTO.getApiSecretKey())) { - throw new LoginException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new LoginException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(searchUserIdDTO.getTel())) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new LoginException(ErrorCode.EMPTY_DATA); } searchUserIdDTO.setTel(encryptUtil.encrypt(searchUserIdDTO.getTel())); List searchIdList = this.myUserIdList(searchUserIdDTO); if(searchIdList.isEmpty()) { - resultCode = ProcessResultCode.NOT_FOUND_ACCOUNT.getResultCode(); - message = ProcessResultCode.NOT_FOUND_ACCOUNT.getMessage(); + resultCode = ErrorCode.NOT_FOUND_ACCOUNT.getCode(); + message = ErrorCode.NOT_FOUND_ACCOUNT.getMessage(); } else { - resultCode = ProcessResultCode.FOUND_ACCOUNT.getResultCode(); - message = ProcessResultCode.FOUND_ACCOUNT.getMessage(); + resultCode = ErrorCode.FOUND_ACCOUNT.getCode(); + message = ErrorCode.FOUND_ACCOUNT.getMessage(); userIdList = searchIdList; } @@ -97,21 +90,12 @@ public ApiResponse updatePasswordProcess(LoginDTO loginDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(loginDTO.getApiSecretKey())) { - throw new LoginException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new LoginException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 - if ( - inputValidate.isEmpty(loginDTO.getUserId()) - || inputValidate.isEmpty(loginDTO.getPassword()) - ) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + if (inputValidate.isEmpty(loginDTO.getUserId()) || inputValidate.isEmpty(loginDTO.getPassword())) { + throw new LoginException(ErrorCode.EMPTY_DATA); } // 3. 비밀번호 암호화 @@ -120,13 +104,10 @@ public ApiResponse updatePasswordProcess(LoginDTO loginDTO) { // 4. 비밀번호 update int updatePassword = this.updatePassword(loginDTO); if(updatePassword > 0) { - resultCode = ProcessResultCode.SUCCESS_UPDATE_PASSWORD.getResultCode(); - message = ProcessResultCode.SUCCESS_UPDATE_PASSWORD.getMessage(); + resultCode = ErrorCode.SUCCESS_UPDATE_PASSWORD.getCode(); + message = ErrorCode.SUCCESS_UPDATE_PASSWORD.getMessage(); } else { - throw new LoginException( - ProcessResultCode.FAIL_UPDATE_PASSWORD.getResultCode(), - ProcessResultCode.FAIL_UPDATE_PASSWORD.getMessage() - ); + throw new LoginException(ErrorCode.FAIL_UPDATE_PASSWORD); } return ApiResponse.result(resultCode, message); @@ -140,10 +121,7 @@ public ApiResponse login(LoginDTO loginDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(loginDTO.getApiSecretKey())) { - throw new LoginException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new LoginException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 @@ -152,28 +130,19 @@ public ApiResponse login(LoginDTO loginDTO) { || inputValidate.isEmpty(loginDTO.getPassword()) ) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new LoginException(ErrorCode.EMPTY_DATA); } // 3. 아이디로 회원정보 조회 Member member = memberService.selectUserMembershipInfo(loginDTO.getUserId()); if (null == member) { - throw new LoginException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - ); + throw new LoginException(ErrorCode.NOT_FOUND_USER_INFO); } // 4. 비밀번호 일치 여부 boolean ok = passwordEncoder.matches(loginDTO.getPassword(), member.getPassword()); if(!ok) { - throw new LoginException( - ProcessResultCode.FAIL_LOGIN.getResultCode(), - ProcessResultCode.FAIL_LOGIN.getMessage() - ); + throw new LoginException(ErrorCode.FAIL_LOGIN); } // 5. ACCESS, REFRESH TOKEN 생성 & REFRESH TOKEN 저장 @@ -202,10 +171,7 @@ public ApiResponse refreshToken(RefreshTokenRequestDTO refreshTokenRequestDTO) { // 1. 필수 입력값 확인 if (inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new LoginException(ErrorCode.EMPTY_DATA); } // 2. ACCESS, REFRESH TOKEN 재생성 @@ -215,7 +181,7 @@ public ApiResponse refreshToken(RefreshTokenRequestDTO refreshTokenRequestDTO) { message = pair.message(); if(!resultCode.equals("200")) { - throw new LoginException(resultCode, message); + return ApiResponse.error(resultCode, message); } data.put("accessToken", pair.accessToken()); @@ -230,19 +196,13 @@ public ApiResponse logout(RefreshTokenRequestDTO refreshTokenRequestDTO) { // 1. 필수 입력값 확인 if(inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new LoginException(ErrorCode.EMPTY_DATA); } // 2. 로그아웃 authService.logout(refreshTokenRequestDTO.getRefreshToken()); // DB REVOKE - return ApiResponse.result( - ProcessResultCode.SUCCESS_LOGOUT.getResultCode(), - ProcessResultCode.SUCCESS_LOGOUT.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_LOGOUT); } public int selectMemberCnt(LoginDTO loginDTO){ diff --git a/src/main/java/com/barogagi/region/controller/RegionController.java b/src/main/java/com/barogagi/region/controller/RegionController.java index e971090..d7b6889 100644 --- a/src/main/java/com/barogagi/region/controller/RegionController.java +++ b/src/main/java/com/barogagi/region/controller/RegionController.java @@ -93,7 +93,7 @@ public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 throw new RuntimeException(e); } finally { - apiResponse.setResultCode(resultCode); + apiResponse.setCode(resultCode); apiResponse.setMessage(message); } return apiResponse; diff --git a/src/main/java/com/barogagi/response/ApiResponse.java b/src/main/java/com/barogagi/response/ApiResponse.java index 9b773b2..8787b49 100644 --- a/src/main/java/com/barogagi/response/ApiResponse.java +++ b/src/main/java/com/barogagi/response/ApiResponse.java @@ -1,14 +1,16 @@ package com.barogagi.response; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; import lombok.Setter; +import org.springframework.http.HttpStatus; @Getter @Setter public class ApiResponse { // 결과 코드 - private String resultCode; + private String code; // 결과 메시지 private String message; @@ -18,7 +20,7 @@ public class ApiResponse { public static ApiResponse success(T data, String message) { ApiResponse res = new ApiResponse<>(); - res.resultCode = "SUCCESS"; + res.code = "SUCCESS"; res.message = message; res.data = data; return res; @@ -26,14 +28,14 @@ public static ApiResponse success(T data, String message) { public static ApiResponse error(String code, String message) { ApiResponse res = new ApiResponse<>(); - res.resultCode = code; + res.code = code; res.message = message; return res; } public static ApiResponse resultData(T data, String code, String message) { ApiResponse res = new ApiResponse<>(); - res.resultCode = code; + res.code = code; res.message = message; res.data = data; return res; @@ -41,8 +43,15 @@ public static ApiResponse resultData(T data, String code, String message) public static ApiResponse result(String code, String message) { ApiResponse res = new ApiResponse<>(); - res.resultCode = code; + res.code = code; res.message = message; return res; } + + public static ApiResponse result(ErrorCode errorCode) { + ApiResponse res = new ApiResponse<>(); + res.code = errorCode.getCode(); + res.message = errorCode.getMessage(); + return res; + } } diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 129ad0a..6d9d037 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -66,23 +66,17 @@ public ApiResponse getScheduleList(HttpServletRequest request) { Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); if (!"200".equals(resultCode)) { - throw new BasicException( - resultCode, - String.valueOf(resultMap.get("message")) - ); + return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ result = scheduleQueryService.getScheduleList(membershipNo); - } catch (BasicException e) { - return ApiResponse.error(e.getResultCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 목록 조회 실패"); } - return ApiResponse.success(result, "일정 목록 조회 성공"); } @@ -99,10 +93,7 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); if (!"200".equals(resultCode)) { - throw new BasicException( - resultCode, - String.valueOf(resultMap.get("message")) - ); + return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); @@ -199,7 +190,7 @@ public ApiResponse createSchedule( result = scheduleCommandService.createSchedule(scheduleRegistReqDTO); } catch (BasicException e) { - return ApiResponse.error(e.getResultCode(), e.getMessage()); + return ApiResponse.error(e.getCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 생성 실패"); } @@ -373,17 +364,12 @@ public ApiResponse saveSchedule( Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); if (!"200".equals(resultCode)) { - throw new BasicException( - resultCode, - String.valueOf(resultMap.get("message")) - ); + return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); result = scheduleCommandService.saveSchedule(scheduleRegistResDTO, membershipNo); - } catch (BasicException e) { - return ApiResponse.error(e.getResultCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 저장 실패"); } @@ -481,7 +467,7 @@ public ApiResponse updateSchedule( result = scheduleCommandService.updateSchedule(scheduleRegistResDTO); } catch (BasicException e) { - return ApiResponse.error(e.getResultCode(), e.getMessage()); + return ApiResponse.error(e.getCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 저장 실패"); } @@ -503,7 +489,7 @@ public ApiResponse deleteSchedule(@Parameter(description = "삭제할 일정 번 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); if (!"200".equals(resultCode)) { - throw new BasicException( resultCode,String.valueOf(resultMap.get("message"))); + return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); diff --git a/src/main/java/com/barogagi/terms/exception/TermsException.java b/src/main/java/com/barogagi/terms/exception/TermsException.java index 9ecb1a7..ed4fc81 100644 --- a/src/main/java/com/barogagi/terms/exception/TermsException.java +++ b/src/main/java/com/barogagi/terms/exception/TermsException.java @@ -1,12 +1,13 @@ package com.barogagi.terms.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class TermsException extends BusinessException { - public TermsException(String resultCode, String message) { - super(resultCode, message); + public TermsException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/terms/service/TermsService.java b/src/main/java/com/barogagi/terms/service/TermsService.java index 3e16b0f..169ce78 100644 --- a/src/main/java/com/barogagi/terms/service/TermsService.java +++ b/src/main/java/com/barogagi/terms/service/TermsService.java @@ -1,6 +1,5 @@ package com.barogagi.terms.service; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.login.dto.LoginVO; import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; @@ -8,8 +7,8 @@ import com.barogagi.terms.exception.TermsException; import com.barogagi.terms.mapper.TermsMapper; import com.barogagi.util.InputValidate; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -44,18 +43,12 @@ public ApiResponse termsListProcess(String apiSecretKey, String termsType) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new TermsException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new TermsException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(termsType)) { - throw new TermsException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new TermsException(ErrorCode.EMPTY_DATA); } // 3. 약관 조회 @@ -64,17 +57,14 @@ public ApiResponse termsListProcess(String apiSecretKey, String termsType) { List termsList = this.selectTermsList(termsInputDTO); if(termsList.isEmpty()) { - throw new TermsException( - ProcessResultCode.NOT_FOUND_TERMS.getResultCode(), - ProcessResultCode.NOT_FOUND_TERMS.getMessage() - ); + throw new TermsException(ErrorCode.NOT_FOUND_TERMS); } return ApiResponse.resultData( termsList, - ProcessResultCode.FOUND_TERMS.getResultCode(), - ProcessResultCode.FOUND_TERMS.getMessage() + ErrorCode.FOUND_TERMS.getCode(), + ErrorCode.FOUND_TERMS.getMessage() ); } @@ -82,30 +72,21 @@ public ApiResponse termsAgreementsProcess(TermsDTO termsDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(termsDTO.getApiSecretKey())) { - throw new TermsException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new TermsException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(termsDTO.getUserId()) || termsDTO.getTermsAgreeList() == null || termsDTO.getTermsAgreeList().isEmpty()) { - throw new TermsException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new TermsException(ErrorCode.EMPTY_DATA); } LoginVO lvo = new LoginVO(); lvo.setUserId(termsDTO.getUserId()); LoginVO loginVO = loginService.findMembershipNo(lvo); if(null == loginVO) { - throw new TermsException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - ); + throw new TermsException(ErrorCode.NOT_FOUND_USER_INFO); } List termsAgreeDTOList = new ArrayList<>(); @@ -121,15 +102,12 @@ public ApiResponse termsAgreementsProcess(TermsDTO termsDTO) { String resCode = this.insertTermsAgreeList(termsAgreeDTOList); if(!resCode.equals("200")) { - throw new TermsException( - ProcessResultCode.FAIL_INSERT_TERMS.getResultCode(), - ProcessResultCode.FAIL_INSERT_TERMS.getMessage() - ); + throw new TermsException(ErrorCode.FAIL_INSERT_TERMS); } return ApiResponse.result( - ProcessResultCode.SUCCESS_INSERT_TERMS.getResultCode(), - ProcessResultCode.SUCCESS_INSERT_TERMS.getMessage() + ErrorCode.SUCCESS_INSERT_TERMS.getCode(), + ErrorCode.SUCCESS_INSERT_TERMS.getMessage() ); } diff --git a/src/main/java/com/barogagi/util/MembershipUtil.java b/src/main/java/com/barogagi/util/MembershipUtil.java index e9b4ebf..92c1959 100644 --- a/src/main/java/com/barogagi/util/MembershipUtil.java +++ b/src/main/java/com/barogagi/util/MembershipUtil.java @@ -1,7 +1,7 @@ package com.barogagi.util; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.exception.BasicException; +import com.barogagi.util.exception.ErrorCode; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; @@ -22,18 +22,15 @@ public Map membershipNoService(HttpServletRequest request) { try { Object membershipNoAttr = request.getAttribute("membershipNo"); if(membershipNoAttr == null) { - throw new BasicException( - ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), - ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() - ); + throw new BasicException(ErrorCode.NOT_EXIST_ACCESS_AUTH); } membershipNo = String.valueOf(membershipNoAttr); - resultCode = ResultCode.EXIST_ACCESS_AUTH.getResultCode(); - message = ResultCode.EXIST_ACCESS_AUTH.getMessage(); + resultCode = ErrorCode.EXIST_ACCESS_AUTH.getCode(); + message = ErrorCode.EXIST_ACCESS_AUTH.getMessage(); } catch (BasicException ex) { - resultCode = ex.getResultCode(); + resultCode = ex.getCode(); message = ex.getMessage(); } finally { resultMap.put("resultCode", resultCode); @@ -48,10 +45,7 @@ public String selectMembershipNo(HttpServletRequest request) { Object membershipNoAttr = request.getAttribute("membershipNo"); if(null == membershipNoAttr) { - throw new BasicException( - ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), - ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() - ); + throw new BasicException(ErrorCode.NOT_EXIST_ACCESS_AUTH); } return String.valueOf(membershipNoAttr); diff --git a/src/main/java/com/barogagi/util/exception/BasicException.java b/src/main/java/com/barogagi/util/exception/BasicException.java index 814c567..196f0c6 100644 --- a/src/main/java/com/barogagi/util/exception/BasicException.java +++ b/src/main/java/com/barogagi/util/exception/BasicException.java @@ -6,20 +6,11 @@ @Getter public class BasicException extends BusinessException { - private final String resultCode; private final ErrorCode errorCode; // 신규 생성자 public BasicException(ErrorCode errorCode) { - super(errorCode.getCode(), errorCode.getMessage()); + super(errorCode); this.errorCode = errorCode; - this.resultCode = errorCode.getCode(); - } - - // 기존 생성자 - public BasicException(String resultCode, String message) { - super(resultCode, message); - this.resultCode = resultCode; - errorCode = null; } } diff --git a/src/main/java/com/barogagi/util/exception/ErrorCode.java b/src/main/java/com/barogagi/util/exception/ErrorCode.java index 9e365af..e07ea93 100644 --- a/src/main/java/com/barogagi/util/exception/ErrorCode.java +++ b/src/main/java/com/barogagi/util/exception/ErrorCode.java @@ -8,9 +8,18 @@ @RequiredArgsConstructor public enum ErrorCode { + // API_SECRET_KEY 일치 X + NOT_EQUAL_API_SECRET_KEY(HttpStatus.UNAUTHORIZED, "A100", "잘못된 접근입니다."), + + // ACCESS TOKEN + NOT_EXIST_ACCESS_AUTH(HttpStatus.UNAUTHORIZED, "A401", "접근 권한이 존재하지 않습니다."), + EXIST_ACCESS_AUTH(HttpStatus.OK, "A200", "회원 번호가 존재합니다."), + EXPIRE_TOKEN(HttpStatus.UNAUTHORIZED, "A300", "Token이 만료되었습니다."), + // Common INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON-500", "서버 오류가 발생했습니다."), INVALID_REQUEST(HttpStatus.BAD_REQUEST, "COMMON-400", "잘못된 요청입니다."), + EMPTY_DATA(HttpStatus.BAD_REQUEST, "C101", "정보를 입력해주세요."), // Membership MEMBERSHIP_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBERSHIP-404", "멤버십 정보가 없습니다."), @@ -28,8 +37,77 @@ public enum ErrorCode { ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "ITEM-001", "아이템 정보를 찾을 수 없습니다."), // Region - REGION_NOT_FOUND(HttpStatus.NOT_FOUND, "REGION-001", "지역 정보를 찾을 수 없습니다."); + REGION_NOT_FOUND(HttpStatus.NOT_FOUND, "REGION-001", "지역 정보를 찾을 수 없습니다."), + + // Nickname + INVALID_NICKNAME(HttpStatus.BAD_REQUEST, "N102", "적합하지 않는 닉네임입니다."), + UNAVAILABLE_NICKNAME(HttpStatus.CONFLICT, "N103", "해당 닉네임 사용이 불가능합니다."), + AVAILABLE_NICKNAME(HttpStatus.OK, "N200", "사용 가능한 닉네임입니다."), + + // UserId + INVALID_USER_ID(HttpStatus.BAD_REQUEST, "U102", "적합한 아이디가 아닙니다."), + UNAVAILABLE_USER_ID(HttpStatus.CONFLICT, "U300", "해당 아이디 사용이 불가능합니다."), + AVAILABLE_USER_ID(HttpStatus.OK, "U200", "해당 아이디 사용이 가능합니다."), + + // SignUp + INVALID_SIGN_UP(HttpStatus.BAD_REQUEST, "S102", "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), + SUCCESS_SIGN_UP(HttpStatus.CREATED, "S200", "회원가입에 성공하였습니다."), + FAIL_SIGN_UP(HttpStatus.INTERNAL_SERVER_ERROR, "S300", "회원가입에 실패하였습니다."), + + // DeleteAccount + SUCCESS_DELETE_ACCOUNT(HttpStatus.OK, "D200", "회원 탈퇴되었습니다."), + FAIL_DELETE_ACCOUNT(HttpStatus.INTERNAL_SERVER_ERROR, "D300", "회원 탈퇴 실패하였습니다."), + + // FindUser + FOUND_ACCOUNT(HttpStatus.OK, "F200", "해당 전화번호로 가입된 아이디가 존재합니다."), + NOT_FOUND_ACCOUNT(HttpStatus.NOT_FOUND, "F201", "해당 전화번호로 가입된 계정이 존재하지 않습니다."), + + // UpdatePassword + SUCCESS_UPDATE_PASSWORD(HttpStatus.OK, "U200", "비밀번호 재설정에 성공하였습니다."), + FAIL_UPDATE_PASSWORD(HttpStatus.INTERNAL_SERVER_ERROR, "U300", "비밀번호 재설정에 실패하였습니다."), + + // Login + NOT_FOUND_USER_INFO(HttpStatus.NOT_FOUND, "L102", "회원 정보가 존재하지 않습니다."), + FAIL_LOGIN(HttpStatus.UNAUTHORIZED, "L103", "로그인에 실패하였습니다."), + SUCCESS_LOGIN(HttpStatus.OK, "L200", "로그인에 성공하였습니다."), + + // RefreshToken + REQUIRED_LOGIN(HttpStatus.UNAUTHORIZED, "R110", "로그인을 진행해주세요."), + REQUIRED_RE_LOGIN(HttpStatus.UNAUTHORIZED, "R120", "로그인을 다시 진행해주세요."), + SUCCESS_REFRESH_TOKEN(HttpStatus.OK, "R200", "토큰이 발급되었습니다."), + FAIL_REFRESH_TOKEN(HttpStatus.INTERNAL_SERVER_ERROR, "R130", "토큰 발급에 실패하였습니다."), + UNAVAILABLE_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "R301", "유효하지 않은 refresh token입니다."), + NOT_FOUND_AVAILABLE_REFRESH_TOKEN(HttpStatus.NOT_FOUND, "R302", "유효한 token 정보를 찾을 수 없습니다."), + + // Logout + FAIL_LOGOUT(HttpStatus.INTERNAL_SERVER_ERROR, "L300", "로그아웃 실패하였습니다."), + SUCCESS_LOGOUT(HttpStatus.OK, "L200", "로그아웃 되었습니다."), + + // Terms + FOUND_TERMS(HttpStatus.OK, "T200", "약관 조회에 성공하였습니다."), + NOT_FOUND_TERMS(HttpStatus.NOT_FOUND, "T102", "약관이 존재하지 않습니다."), + SUCCESS_INSERT_TERMS(HttpStatus.CREATED, "T200", "약관 저장에 성공하였습니다."), + FAIL_INSERT_TERMS(HttpStatus.INTERNAL_SERVER_ERROR, "T300", "약관 저장에 실패하였습니다."), + + // MemberInfo + FOUND_USER_INFO(HttpStatus.OK, "M200", "회원 정보 조회가 완료되었습니다."), + FAIL_UPDATE_USER_INFO(HttpStatus.INTERNAL_SERVER_ERROR, "M404", "사용자 정보 수정 실패하였습니다."), + SUCCESS_UPDATE_USER_INFO(HttpStatus.OK, "M200", "사용자 정보 수정 완료하였습니다."), + + // MainPage + NOT_FOUND_SCHEDULE(HttpStatus.NOT_FOUND, "M201", "일정이 존재하지 않습니다."), + FOUND_SCHEDULE(HttpStatus.OK, "M200", "조회 성공하였습니다."), + NOT_FOUND_POPULAR_TAG(HttpStatus.NOT_FOUND, "M201", "인기 태그 목록이 존재하지 않습니다."), + FOUND_POPULAR_TAG(HttpStatus.OK, "M200", "인기 태그 조회 완료하였습니다."), + NOT_FOUND_POPULAR_REGION(HttpStatus.NOT_FOUND, "M201", "인기 지역 목록이 존재하지 않습니다."), + FOUND_POPULAR_REGION(HttpStatus.OK, "M200", "인기 지역 조회 완료하였습니다."), + // Approval + SUCCESS_SEND_SMS(HttpStatus.OK, "A200", "인증번호 발송에 성공하었습니다."), + FAIL_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A103", "인증번호 발송에 실패하였습니다."), + ERROR_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A102", "오류가 발생하였습니다."), + SUCCESS_CHECK_SMS(HttpStatus.OK, "A200", "인증이 완료되었습니다."), + FAIL_CHECK_SMS(HttpStatus.BAD_REQUEST, "A300", "인증에 실패하였습니다."); private final HttpStatus status; private final String code; diff --git a/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java index db6f7ea..8bd8a60 100644 --- a/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java @@ -35,6 +35,6 @@ public ResponseEntity> handleUnknown(Exception e) { @ExceptionHandler(BusinessException.class) public ApiResponse handleBusinessException(BusinessException e) { - return ApiResponse.result(e.getResultCode(), e.getMessage()); + return ApiResponse.result(e.getCode(), e.getMessage()); } } From f0f290b4038c338a9a2f561105db2f1179ab36fe Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 29 Dec 2025 20:53:57 +0900 Subject: [PATCH 58/79] =?UTF-8?q?[FIX]=20=EC=97=90=EB=9F=AC=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=ED=98=95=EC=8B=9D=20=EB=B3=80=EA=B2=BD=20(#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FEAT : 로그 공통화 & 코드 리팩토링 (#39) * [FEAT] : 로그 공통화 작업중 & 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 일반 로그인 & 토큰 재발급 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 약관 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 회원정보 코드 리팩토링 중 * [FEAT] : 로그 공통화 작업중 & 회원정보 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 메인 화면 api 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 인증 api 코드 리팩토링 * [FEAT] : 필요없는 import 삭제 * [FEAT] : 필요없는 import 삭제 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FIX] : 에러 코드 형식 변경 (#41) --- .../approval/exception/ApprovalException.java | 5 +- .../approval/service/ApprovalService.java | 49 ++------ .../com/barogagi/config/JwtAuthFilter.java | 21 +--- .../com/barogagi/config/SecurityConfig.java | 6 +- .../config/exception/BusinessException.java | 14 ++- .../logging/ServiceLoggingAspect.java | 2 +- .../mainPage/exception/MainPageException.java | 6 +- .../mainPage/service/MainPageService.java | 46 +++----- .../basic/join/controller/JoinController.java | 10 +- .../basic/join/exception/JoinException.java | 5 +- .../basic/join/service/BasicJoinService.java | 111 ++++-------------- .../info/exception/MemberInfoException.java | 5 +- .../member/info/service/MemberService.java | 39 ++---- .../InvalidRefreshTokenException.java | 5 +- .../login/exception/LoginException.java | 5 +- .../member/login/service/AuthService.java | 37 +++--- .../member/login/service/LoginService.java | 82 ++++--------- .../region/controller/RegionController.java | 2 +- .../com/barogagi/response/ApiResponse.java | 19 ++- .../controller/ScheduleController.java | 26 +--- .../terms/exception/TermsException.java | 5 +- .../barogagi/terms/service/TermsService.java | 46 ++------ .../com/barogagi/util/MembershipUtil.java | 18 +-- .../util/exception/BasicException.java | 11 +- .../barogagi/util/exception/ErrorCode.java | 80 ++++++++++++- .../exception/GlobalExceptionHandler.java | 2 +- 26 files changed, 265 insertions(+), 392 deletions(-) diff --git a/src/main/java/com/barogagi/approval/exception/ApprovalException.java b/src/main/java/com/barogagi/approval/exception/ApprovalException.java index 5dde8e6..62b8ca6 100644 --- a/src/main/java/com/barogagi/approval/exception/ApprovalException.java +++ b/src/main/java/com/barogagi/approval/exception/ApprovalException.java @@ -1,12 +1,13 @@ package com.barogagi.approval.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class ApprovalException extends BusinessException { - public ApprovalException(String resultCode, String message) { - super(resultCode, message); + public ApprovalException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/approval/service/ApprovalService.java b/src/main/java/com/barogagi/approval/service/ApprovalService.java index 6cb6f64..feb3ed6 100644 --- a/src/main/java/com/barogagi/approval/service/ApprovalService.java +++ b/src/main/java/com/barogagi/approval/service/ApprovalService.java @@ -5,17 +5,15 @@ import com.barogagi.approval.vo.ApprovalCompleteVO; import com.barogagi.approval.vo.ApprovalSendVO; import com.barogagi.approval.vo.ApprovalVO; -import com.barogagi.config.resultCode.ProcessResultCode; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.response.ApiResponse; import com.barogagi.sendSms.dto.SendSmsVO; import com.barogagi.sendSms.service.SendSmsService; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.web.bind.annotation.RequestBody; @Service public class ApprovalService { @@ -49,18 +47,12 @@ public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(approvalSendVO.getApiSecretKey())) { - throw new ApprovalException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new ApprovalException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(approvalSendVO.getTel())) { - throw new ApprovalException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new ApprovalException(ErrorCode.EMPTY_DATA); } // 3. 처리 @@ -85,10 +77,7 @@ public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { boolean sendMessageResult = sendSmsService.sendSms(sendSmsVO); if(!sendMessageResult) { - throw new ApprovalException( - ProcessResultCode.FAIL_SEND_SMS.getResultCode(), - ProcessResultCode.FAIL_SEND_SMS.getMessage() - ); + throw new ApprovalException(ErrorCode.FAIL_SEND_SMS); } // 인증번호 암호화 @@ -98,35 +87,23 @@ public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { int insertResult = this.insertApprovalRecord(approvalVO); if(insertResult <= 0) { - throw new ApprovalException( - ProcessResultCode.ERROR_SEND_SMS.getResultCode(), - ProcessResultCode.ERROR_SEND_SMS.getMessage() - ); + throw new ApprovalException(ErrorCode.ERROR_SEND_SMS); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_SEND_SMS.getResultCode(), - ProcessResultCode.SUCCESS_SEND_SMS.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_SEND_SMS); } public ApiResponse approvalTelCheck(ApprovalCompleteVO approvalCompleteVO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(approvalCompleteVO.getApiSecretKey())) { - throw new ApprovalException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new ApprovalException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(approvalCompleteVO.getAuthCode()) || inputValidate.isEmpty(approvalCompleteVO.getTel())) { - throw new ApprovalException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new ApprovalException(ErrorCode.EMPTY_DATA); } // 3. 전화번호 암호화 @@ -139,16 +116,10 @@ public ApiResponse approvalTelCheck(ApprovalCompleteVO approvalCompleteVO) { // 4. 인증 int updateResult = this.updateApprovalComplete(approvalVO); if(updateResult != 1){ - throw new ApprovalException( - ProcessResultCode.FAIL_CHECK_SMS.getResultCode(), - ProcessResultCode.FAIL_CHECK_SMS.getMessage() - ); + throw new ApprovalException(ErrorCode.FAIL_CHECK_SMS); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_CHECK_SMS.getResultCode(), - ProcessResultCode.SUCCESS_CHECK_SMS.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_CHECK_SMS); } public int updateApprovalRecord(ApprovalVO vo){ diff --git a/src/main/java/com/barogagi/config/JwtAuthFilter.java b/src/main/java/com/barogagi/config/JwtAuthFilter.java index 40c4f70..233b7df 100644 --- a/src/main/java/com/barogagi/config/JwtAuthFilter.java +++ b/src/main/java/com/barogagi/config/JwtAuthFilter.java @@ -4,7 +4,7 @@ import com.barogagi.member.info.service.MemberService; import com.barogagi.member.login.exception.InvalidRefreshTokenException; import com.barogagi.util.JwtUtil; -import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.exception.ErrorCode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.*; @@ -53,21 +53,12 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, chain.doFilter(req, res); } catch (ExpiredJwtException e) { // 유효기간이 지나서 만료된 경우 - writeErrorResponse( - ResultCode.EXPIRE_TOKEN.getResultCode(), - ResultCode.EXPIRE_TOKEN.getMessage() - ); + writeErrorResponse(ErrorCode.EXPIRE_TOKEN); } catch (JwtException | SecurityException e) { // 위조되었거나 변조되었거나 구조가 잘못되었을 경우 - writeErrorResponse( - ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), - ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() - ); + writeErrorResponse(ErrorCode.NOT_EXIST_ACCESS_AUTH); } catch (Exception e) { - writeErrorResponse( - ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), - ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() - ); + writeErrorResponse(ErrorCode.NOT_EXIST_ACCESS_AUTH); } } @@ -78,8 +69,8 @@ protected boolean shouldNotFilter(HttpServletRequest request) { return p.startsWith("/auth/") || p.startsWith("/login/basic/membership/userId/search"); } - private void writeErrorResponse(String resultCode, String message) throws IOException { - throw new InvalidRefreshTokenException(resultCode, message); + private void writeErrorResponse(ErrorCode errorCode) throws IOException { + throw new InvalidRefreshTokenException(errorCode); } } diff --git a/src/main/java/com/barogagi/config/SecurityConfig.java b/src/main/java/com/barogagi/config/SecurityConfig.java index b8952a7..0be7381 100644 --- a/src/main/java/com/barogagi/config/SecurityConfig.java +++ b/src/main/java/com/barogagi/config/SecurityConfig.java @@ -2,7 +2,7 @@ import com.barogagi.member.oauth.join.service.CustomOidcUserService; import com.barogagi.member.oauth.join.service.DelegatingOAuth2UserService; -import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.exception.ErrorCode; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,8 +77,8 @@ SecurityFilterChain filterChain( // 브라우저 리다이렉트 대신 401 JSON .exceptionHandling(ex -> ex.authenticationEntryPoint((req, res, e) -> { - String resultCode = ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(); - String message = ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage(); + String resultCode = ErrorCode.NOT_EXIST_ACCESS_AUTH.getCode(); + String message = ErrorCode.NOT_EXIST_ACCESS_AUTH.getMessage(); res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); res.setContentType("application/json;charset=UTF-8"); diff --git a/src/main/java/com/barogagi/config/exception/BusinessException.java b/src/main/java/com/barogagi/config/exception/BusinessException.java index a87dcdf..0ef97df 100644 --- a/src/main/java/com/barogagi/config/exception/BusinessException.java +++ b/src/main/java/com/barogagi/config/exception/BusinessException.java @@ -1,14 +1,20 @@ package com.barogagi.config.exception; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; +import org.springframework.http.HttpStatus; @Getter public abstract class BusinessException extends RuntimeException { - private final String resultCode; + private final HttpStatus httpStatus; + private final String code; + private final String message; - public BusinessException(String resultCode, String message) { - super(message); - this.resultCode = resultCode; + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.httpStatus = errorCode.getStatus(); + this.code = errorCode.getCode(); + this.message = errorCode.getMessage(); } } diff --git a/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java b/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java index 0e43bf7..d32a1da 100644 --- a/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java +++ b/src/main/java/com/barogagi/logging/ServiceLoggingAspect.java @@ -32,7 +32,7 @@ public Object logService(ProceedingJoinPoint joinPoint) throws Throwable { return result; } catch (BusinessException ex) { - log.error("Service BusinessException - {}.{} / resultCode - {} / message - {}", className, methodName, ex.getResultCode(), ex.getMessage(), ex); + log.error("Service BusinessException - {}.{} / httpStatus - {} / errorCode - {} / message - {}", className, methodName, ex.getHttpStatus(), ex.getCode(), ex.getMessage(), ex); throw ex; } catch (Exception e) { diff --git a/src/main/java/com/barogagi/mainPage/exception/MainPageException.java b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java index 11e04cf..c08b15e 100644 --- a/src/main/java/com/barogagi/mainPage/exception/MainPageException.java +++ b/src/main/java/com/barogagi/mainPage/exception/MainPageException.java @@ -1,12 +1,12 @@ package com.barogagi.mainPage.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class MainPageException extends BusinessException { - - public MainPageException(String resultCode, String message) { - super(resultCode, message); + public MainPageException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/mainPage/service/MainPageService.java b/src/main/java/com/barogagi/mainPage/service/MainPageService.java index 3f766c4..7358c32 100644 --- a/src/main/java/com/barogagi/mainPage/service/MainPageService.java +++ b/src/main/java/com/barogagi/mainPage/service/MainPageService.java @@ -4,11 +4,10 @@ import com.barogagi.mainPage.exception.MainPageException; import com.barogagi.mainPage.mapper.MainPageMapper; import com.barogagi.mainPage.response.MainPageResponse; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.response.ApiResponse; import com.barogagi.util.MembershipUtil; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import jakarta.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,11 +44,14 @@ public MainPageResponse selectUserScheduleInfoProcess(HttpServletRequest request String message = ""; List tagList = null; RegionInfoDTO regionInfo = null; + UserInfoResponseDTO userInfoResponseDTO = null; // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MainPageException( + + return MainPageResponse.resultData( + userInfoResponseDTO, tagList, regionInfo, String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message")) ); @@ -60,15 +62,15 @@ public MainPageResponse selectUserScheduleInfoProcess(HttpServletRequest request // 2. 유저 일정 정보 조회 UserInfoRequestDTO userInfoRequestDTO = new UserInfoRequestDTO(); userInfoRequestDTO.setMembershipNo(membershipNo); - UserInfoResponseDTO userInfoResponseDTO = this.selectUserScheduleInfo(userInfoRequestDTO); + userInfoResponseDTO = this.selectUserScheduleInfo(userInfoRequestDTO); if(null == userInfoResponseDTO) { - resultCode = ProcessResultCode.NOT_FOUND_SCHEDULE.getResultCode(); - message = ProcessResultCode.NOT_FOUND_SCHEDULE.getMessage(); + resultCode = ErrorCode.NOT_FOUND_SCHEDULE.getCode(); + message = ErrorCode.NOT_FOUND_SCHEDULE.getMessage(); } else { - resultCode = ProcessResultCode.FOUND_SCHEDULE.getResultCode(); - message = ProcessResultCode.FOUND_SCHEDULE.getMessage(); + resultCode = ErrorCode.FOUND_SCHEDULE.getCode(); + message = ErrorCode.FOUND_SCHEDULE.getMessage(); // 3. 해당 schedule에 대한 태그 목록 조회 userInfoRequestDTO.setScheduleNum(userInfoResponseDTO.getScheduleNum()); @@ -86,26 +88,20 @@ public ApiResponse selectPopularTagList(String apiSecretKey) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new MainPageException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new MainPageException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 인기 태그 조회 List tagRankInfoList = this.selectTagRankList(); if(tagRankInfoList.isEmpty()) { - throw new MainPageException( - ProcessResultCode.NOT_FOUND_POPULAR_TAG.getResultCode(), - ProcessResultCode.NOT_FOUND_POPULAR_TAG.getMessage() - ); + throw new MainPageException(ErrorCode.NOT_FOUND_POPULAR_TAG); } return ApiResponse.resultData( tagRankInfoList, - ProcessResultCode.FOUND_POPULAR_TAG.getResultCode(), - ProcessResultCode.FOUND_POPULAR_TAG.getMessage() + ErrorCode.FOUND_POPULAR_TAG.getCode(), + ErrorCode.FOUND_POPULAR_TAG.getMessage() ); } @@ -113,26 +109,20 @@ public ApiResponse selectPopularRegionList(String apiSecretKey) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new MainPageException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new MainPageException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 인기 지역 조회 List regionRankInfoList = this.selectRegionRankList(); if(regionRankInfoList.isEmpty()) { - throw new MainPageException( - ProcessResultCode.NOT_FOUND_POPULAR_REGION.getResultCode(), - ProcessResultCode.NOT_FOUND_POPULAR_REGION.getMessage() - ); + throw new MainPageException(ErrorCode.NOT_FOUND_POPULAR_REGION); } return ApiResponse.resultData( regionRankInfoList, - ProcessResultCode.FOUND_POPULAR_REGION.getResultCode(), - ProcessResultCode.FOUND_POPULAR_REGION.getMessage() + ErrorCode.FOUND_POPULAR_REGION.getCode(), + ErrorCode.FOUND_POPULAR_REGION.getMessage() ); } diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index 7641ee8..1a2e02f 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -23,11 +23,11 @@ public JoinController(BasicJoinService basicJoinService) { @Operation(summary = "아이디 중복 체크 기능", description = "아이디 중복 체크 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 아이디 사용이 가능합니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디가 아닙니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "해당 아이디 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U200", description = "해당 아이디 사용이 가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U102", description = "적합한 아이디가 아닙니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U300", description = "해당 아이디 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/userid/exists") diff --git a/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java b/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java index 6c1b42f..a82a81c 100644 --- a/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java +++ b/src/main/java/com/barogagi/member/basic/join/exception/JoinException.java @@ -1,12 +1,13 @@ package com.barogagi.member.basic.join.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class JoinException extends BusinessException { - public JoinException(String resultCode, String message) { - super(resultCode, message); + public JoinException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java b/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java index 2ef28de..60b2a03 100644 --- a/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java +++ b/src/main/java/com/barogagi/member/basic/join/service/BasicJoinService.java @@ -1,20 +1,18 @@ package com.barogagi.member.basic.join.service; import com.barogagi.config.PasswordConfig; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.basic.join.dto.JoinDTO; import com.barogagi.member.basic.join.dto.JoinRequestDTO; import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.exception.JoinException; import com.barogagi.member.login.dto.RefreshTokenRequestDTO; -import com.barogagi.member.login.exception.InvalidRefreshTokenException; import com.barogagi.member.login.service.AccountService; import com.barogagi.member.login.service.AuthService; import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -54,26 +52,17 @@ public ApiResponse checkNickname(String apiSecretKey, String nickname) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new JoinException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new JoinException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(nickname)) { - throw new JoinException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new JoinException(ErrorCode.EMPTY_DATA); } // 3. 적합한 닉네임인지 확인 if(!validator.isValidNickname(nickname)) { - throw new JoinException( - ProcessResultCode.INVALID_NICKNAME.getResultCode(), - ProcessResultCode.INVALID_NICKNAME.getMessage() - ); + throw new JoinException(ErrorCode.INVALID_NICKNAME); } // 4. 닉네임 중복 체크 @@ -82,16 +71,10 @@ public ApiResponse checkNickname(String apiSecretKey, String nickname) { int nickNameCnt = joinService.selectNicknameCnt(nickNameDTO); if(nickNameCnt > 0) { - throw new JoinException( - ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), - ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() - ); + throw new JoinException(ErrorCode.UNAVAILABLE_NICKNAME); } - return ApiResponse.result( - ProcessResultCode.AVAILABLE_NICKNAME.getResultCode(), - ProcessResultCode.AVAILABLE_NICKNAME.getMessage() - ); + return ApiResponse.result(ErrorCode.AVAILABLE_NICKNAME); } // 아이디 중복 체크 service @@ -99,26 +82,17 @@ public ApiResponse checkUserId(String apiSecretKey, String userId) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new JoinException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new JoinException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(userId)) { - throw new JoinException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new JoinException(ErrorCode.EMPTY_DATA); } // 3. 적합한 아이디인지 확인 if(!validator.isValidId(userId)) { - throw new JoinException( - ProcessResultCode.INVALID_USER_ID.getResultCode(), - ProcessResultCode.INVALID_USER_ID.getMessage() - ); + throw new JoinException(ErrorCode.INVALID_USER_ID); } // 4. 아이디 중복 체크 @@ -128,26 +102,17 @@ public ApiResponse checkUserId(String apiSecretKey, String userId) { int checkUserId = joinService.selectUserIdCnt(joinDTO); if(checkUserId > 0) { - throw new JoinException( - ProcessResultCode.UNAVAILABLE_USER_ID.getResultCode(), - ProcessResultCode.UNAVAILABLE_USER_ID.getMessage() - ); + throw new JoinException(ErrorCode.UNAVAILABLE_USER_ID); } - return ApiResponse.result( - ProcessResultCode.AVAILABLE_USER_ID.getResultCode(), - ProcessResultCode.AVAILABLE_USER_ID.getMessage() - ); + return ApiResponse.result(ErrorCode.AVAILABLE_USER_ID); } public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(joinRequestDTO.getApiSecretKey())) { - throw new JoinException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new JoinException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 @@ -157,10 +122,7 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { || inputValidate.isEmpty(joinRequestDTO.getPassword()) || inputValidate.isEmpty(joinRequestDTO.getTel())) { - throw new JoinException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new JoinException(ErrorCode.EMPTY_DATA); } // 3. 적합한 아이디인지 확인 @@ -169,10 +131,7 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { validator.isValidId(joinRequestDTO.getUserId()) && validator.isValidPassword(joinRequestDTO.getPassword())) ) { - throw new JoinException( - ProcessResultCode.INVALID_SIGN_UP.getResultCode(), - ProcessResultCode.INVALID_SIGN_UP.getMessage() - ); + throw new JoinException(ErrorCode.INVALID_SIGN_UP); } // 4. 암호화 @@ -205,10 +164,7 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { // 5. 아이디 중복 검증 if(duplicateUserId > 0) { - throw new JoinException( - ProcessResultCode.UNAVAILABLE_USER_ID.getResultCode(), - ProcessResultCode.UNAVAILABLE_USER_ID.getMessage() - ); + throw new JoinException(ErrorCode.UNAVAILABLE_USER_ID); } // 닉네임 값이 넘어올 경우 중복 검사 @@ -216,10 +172,7 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { // 닉네임 적합성 검사 if(!validator.isValidNickname(joinRequestDTO.getNickName())) { - throw new JoinException( - ProcessResultCode.INVALID_NICKNAME.getResultCode(), - ProcessResultCode.INVALID_NICKNAME.getMessage() - ); + throw new JoinException(ErrorCode.INVALID_NICKNAME); } NickNameDTO nickNameDTO = new NickNameDTO(); @@ -227,41 +180,31 @@ public ApiResponse signUp(JoinRequestDTO joinRequestDTO) { int selectNicknameCnt = joinService.selectNicknameCnt(nickNameDTO); if(selectNicknameCnt > 0) { - throw new JoinException( - ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), - ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() - ); + throw new JoinException(ErrorCode.UNAVAILABLE_NICKNAME); } } // 6. 회원 정보 저장 int insertResult = joinService.insertMembershipInfo(joinDTO); if(insertResult <= 0){ - throw new JoinException( - ProcessResultCode.FAIL_SIGN_UP.getResultCode(), - ProcessResultCode.FAIL_SIGN_UP.getMessage() - ); + throw new JoinException(ErrorCode.FAIL_SIGN_UP); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_SIGN_UP.getResultCode(), - ProcessResultCode.SUCCESS_SIGN_UP.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_SIGN_UP); } public ApiResponse deleteAccount(RefreshTokenRequestDTO refreshTokenRequestDTO) { // 1. refresh token이 공백 또는 null인지 확인 if(inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { - throw new JoinException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage()); + throw new JoinException(ErrorCode.EMPTY_DATA); } // 2. refresh token을 이용해서 membershipNo 구하기 Map resultMap = authService.selectUserInfoByToken(refreshTokenRequestDTO.getRefreshToken()); if(!resultMap.get("resultCode").equals("200")) { - throw new InvalidRefreshTokenException( + + return ApiResponse.error( resultMap.get("resultCode"), resultMap.get("message") ); @@ -269,16 +212,10 @@ public ApiResponse deleteAccount(RefreshTokenRequestDTO refreshTokenRequestDTO) int deleteResult = accountService.deleteMyAccount(resultMap.get("membershipNo")); if(deleteResult <= 0) { - throw new JoinException( - ProcessResultCode.FAIL_DELETE_ACCOUNT.getResultCode(), - ProcessResultCode.FAIL_DELETE_ACCOUNT.getMessage() - ); + throw new JoinException(ErrorCode.FAIL_DELETE_ACCOUNT); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_DELETE_ACCOUNT.getResultCode(), - ProcessResultCode.SUCCESS_DELETE_ACCOUNT.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_DELETE_ACCOUNT); } } diff --git a/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java index 8888ead..952c4d3 100644 --- a/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java +++ b/src/main/java/com/barogagi/member/info/exception/MemberInfoException.java @@ -1,12 +1,13 @@ package com.barogagi.member.info.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class MemberInfoException extends BusinessException { - public MemberInfoException(String resultCode, String message) { - super(resultCode, message); + public MemberInfoException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/member/info/service/MemberService.java b/src/main/java/com/barogagi/member/info/service/MemberService.java index 1efbe21..88845ee 100644 --- a/src/main/java/com/barogagi/member/info/service/MemberService.java +++ b/src/main/java/com/barogagi/member/info/service/MemberService.java @@ -1,6 +1,5 @@ package com.barogagi.member.info.service; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.basic.join.dto.NickNameDTO; import com.barogagi.member.basic.join.service.JoinService; import com.barogagi.member.info.dto.Member; @@ -11,7 +10,7 @@ import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; import com.barogagi.util.MembershipUtil; -import com.barogagi.config.resultCode.ResultCode; +import com.barogagi.util.exception.ErrorCode; import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -46,7 +45,7 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MemberInfoException( + return ApiResponse.error( String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message")) ); @@ -56,10 +55,7 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { // 2. 회원 정보 조회 Member memberInfo = this.findByMembershipNo(membershipNo); if(null == memberInfo) { - throw new MemberInfoException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - ); + throw new MemberInfoException(ErrorCode.NOT_FOUND_USER_INFO); } // 이메일 복호화 @@ -73,8 +69,8 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { return ApiResponse.resultData( memberInfo, - ProcessResultCode.FOUND_USER_INFO.getResultCode(), - ProcessResultCode.FOUND_USER_INFO.getMessage() + ErrorCode.FOUND_USER_INFO.getCode(), + ErrorCode.FOUND_USER_INFO.getMessage() ); } @@ -83,9 +79,10 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); if(!membershipNoInfo.get("resultCode").equals("200")) { - throw new MemberInfoException( + return ApiResponse.error( String.valueOf(membershipNoInfo.get("resultCode")), - String.valueOf(membershipNoInfo.get("message"))); + String.valueOf(membershipNoInfo.get("message")) + ); } String membershipNo = String.valueOf(membershipNoInfo.get("membershipNo")); @@ -93,10 +90,7 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest // 2. 회원 정보 조회 Member memberInfo = this.findByMembershipNo(membershipNo); if(null == memberInfo) { - throw new MemberInfoException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - ); + throw new MemberInfoException(ErrorCode.NOT_FOUND_USER_INFO); } // 3. 데이터 처리 @@ -117,10 +111,7 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest int nickNameCnt = joinService.selectNicknameCnt(nickNameRequest); if(nickNameCnt > 0) { - throw new MemberInfoException( - ProcessResultCode.UNAVAILABLE_NICKNAME.getResultCode(), - ProcessResultCode.UNAVAILABLE_NICKNAME.getMessage() - ); + throw new MemberInfoException(ErrorCode.UNAVAILABLE_NICKNAME); } memberInfo.setNickName(memberRequestDTO.getNickName()); @@ -128,16 +119,10 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest int updateMemberInfo = this.updateMemberInfo(memberInfo); if(updateMemberInfo <= 0) { - throw new MemberInfoException( - ProcessResultCode.FAIL_UPDATE_USER_INFO.getResultCode(), - ProcessResultCode.FAIL_UPDATE_USER_INFO.getMessage() - ); + throw new MemberInfoException(ErrorCode.FAIL_UPDATE_USER_INFO); } - return ApiResponse.result( - ProcessResultCode.SUCCESS_UPDATE_USER_INFO.getResultCode(), - ProcessResultCode.SUCCESS_UPDATE_USER_INFO.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_UPDATE_USER_INFO); } public Member findByMembershipNo(String membershipNo) { diff --git a/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java b/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java index 2c89470..5c4c491 100644 --- a/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java +++ b/src/main/java/com/barogagi/member/login/exception/InvalidRefreshTokenException.java @@ -1,12 +1,13 @@ package com.barogagi.member.login.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class InvalidRefreshTokenException extends BusinessException { - public InvalidRefreshTokenException(String resultCode, String message) { - super(resultCode, message); + public InvalidRefreshTokenException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/member/login/exception/LoginException.java b/src/main/java/com/barogagi/member/login/exception/LoginException.java index 84a44f9..ca5c843 100644 --- a/src/main/java/com/barogagi/member/login/exception/LoginException.java +++ b/src/main/java/com/barogagi/member/login/exception/LoginException.java @@ -1,12 +1,13 @@ package com.barogagi.member.login.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class LoginException extends BusinessException { - public LoginException(String resultCode, String message) { - super(resultCode, message); + public LoginException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/member/login/service/AuthService.java b/src/main/java/com/barogagi/member/login/service/AuthService.java index 148df4b..8e733e8 100644 --- a/src/main/java/com/barogagi/member/login/service/AuthService.java +++ b/src/main/java/com/barogagi/member/login/service/AuthService.java @@ -1,6 +1,5 @@ package com.barogagi.member.login.service; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.login.dto.*; import com.barogagi.member.login.entity.RefreshToken; import com.barogagi.member.login.entity.UserMembership; @@ -9,6 +8,7 @@ import com.barogagi.member.login.repository.RefreshTokenRepository; import com.barogagi.member.login.repository.UserMembershipRepository; import com.barogagi.util.JwtUtil; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.crypto.password.PasswordEncoder; @@ -82,8 +82,8 @@ public LoginResponse login(LoginRequest req) { accessExp, refresh, refreshExp, - ProcessResultCode.SUCCESS_LOGIN.getResultCode(), - ProcessResultCode.SUCCESS_LOGIN.getMessage() + ErrorCode.SUCCESS_LOGIN.getCode(), + ErrorCode.SUCCESS_LOGIN.getMessage() ), no, u.getUserId(), u.getJoinType() ); @@ -114,8 +114,8 @@ public LoginResponse loginAfterSignup(String userId, String deviceId) { accessExp, refresh, refreshExp, - ProcessResultCode.SUCCESS_REFRESH_TOKEN.getResultCode(), - ProcessResultCode.SUCCESS_REFRESH_TOKEN.getMessage() + ErrorCode.SUCCESS_REFRESH_TOKEN.getCode(), + ErrorCode.SUCCESS_REFRESH_TOKEN.getMessage() ), no, u.getUserId(), u.getJoinType() ); @@ -147,19 +147,13 @@ public TokenPair rotate(String refreshToken) { // 현재 리프레시가 DB에 VALID로 존재하는지 확인 RefreshToken current = refreshRepo.findByTokenAndStatus(refreshToken, "VALID") - .orElseThrow(() -> new InvalidRefreshTokenException( - ProcessResultCode.REQUIRED_LOGIN.getResultCode(), - ProcessResultCode.REQUIRED_LOGIN.getMessage() - )); + .orElseThrow(() -> new InvalidRefreshTokenException(ErrorCode.REQUIRED_LOGIN)); // 만료 체크 if (current.getExpiresAt().isBefore(LocalDateTime.now())) { current.setStatus("REVOKED"); refreshRepo.save(current); - throw new InvalidRefreshTokenException( - ProcessResultCode.REQUIRED_RE_LOGIN.getResultCode(), - ProcessResultCode.REQUIRED_RE_LOGIN.getMessage() - ); + throw new InvalidRefreshTokenException(ErrorCode.REQUIRED_RE_LOGIN); } // 같은 멤버/디바이스의 기존 VALID 토큰들 모두 REVOKE (동시 세션 차단용) @@ -171,10 +165,7 @@ public TokenPair rotate(String refreshToken) { // 새 토큰 발급 UserMembership user = userRepo.findById(membershipNo) - .orElseThrow(() -> new InvalidRefreshTokenException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - )); + .orElseThrow(() -> new InvalidRefreshTokenException(ErrorCode.NOT_FOUND_USER_INFO)); newAccess = jwt.generateAccessToken(membershipNo, user.getUserId()); newRefresh = jwt.generateRefreshToken(membershipNo, deviceId); @@ -194,7 +185,7 @@ public TokenPair rotate(String refreshToken) { 0, "", 0, - ex.getResultCode(), + ex.getCode(), ex.getMessage() ); } @@ -202,8 +193,8 @@ public TokenPair rotate(String refreshToken) { return new TokenPair( newAccess, jwt.getAccessExpSeconds(), newRefresh, jwt.getRefreshExpSeconds(), - ProcessResultCode.SUCCESS_REFRESH_TOKEN.getResultCode(), - ProcessResultCode.SUCCESS_REFRESH_TOKEN.getMessage() + ErrorCode.SUCCESS_REFRESH_TOKEN.getCode(), + ErrorCode.SUCCESS_REFRESH_TOKEN.getMessage() ); } @@ -242,7 +233,7 @@ public Map selectUserInfoByToken(String refreshToken) { try { // 1. JWT 토큰 유효성 검증 if(!jwt.isTokenValid(refreshToken) || !jwt.isRefreshToken(refreshToken)) { - throw new InvalidRefreshTokenException("301", "유효하지 않은 refresh token입니다."); + throw new InvalidRefreshTokenException(ErrorCode.UNAVAILABLE_REFRESH_TOKEN); } // 2. membershipNo 구하기 @@ -250,7 +241,7 @@ public Map selectUserInfoByToken(String refreshToken) { // 3. membershipNo 조회가 되지 않을 경우 if(membershipNo == null || membershipNo.isBlank()) { - throw new InvalidRefreshTokenException("302", "유효한 token 정보를 찾을 수 없습니다."); + throw new InvalidRefreshTokenException(ErrorCode.NOT_FOUND_AVAILABLE_REFRESH_TOKEN); } resultCode = "200"; @@ -258,7 +249,7 @@ public Map selectUserInfoByToken(String refreshToken) { returnMap.put("membershipNo", membershipNo); } catch (InvalidRefreshTokenException e) { - resultCode = e.getResultCode(); + resultCode = e.getCode(); message = e.getMessage(); } finally { returnMap.put("resultCode", resultCode); diff --git a/src/main/java/com/barogagi/member/login/service/LoginService.java b/src/main/java/com/barogagi/member/login/service/LoginService.java index 8300cc5..7dd60fe 100644 --- a/src/main/java/com/barogagi/member/login/service/LoginService.java +++ b/src/main/java/com/barogagi/member/login/service/LoginService.java @@ -1,7 +1,6 @@ package com.barogagi.member.login.service; import com.barogagi.config.PasswordConfig; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.info.dto.Member; import com.barogagi.member.info.service.MemberService; import com.barogagi.member.login.dto.*; @@ -10,8 +9,8 @@ import com.barogagi.response.ApiResponse; import com.barogagi.util.EncryptUtil; import com.barogagi.util.InputValidate; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -62,29 +61,23 @@ public ApiResponse findUser(SearchUserIdDTO searchUserIdDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(searchUserIdDTO.getApiSecretKey())) { - throw new LoginException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new LoginException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(searchUserIdDTO.getTel())) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new LoginException(ErrorCode.EMPTY_DATA); } searchUserIdDTO.setTel(encryptUtil.encrypt(searchUserIdDTO.getTel())); List searchIdList = this.myUserIdList(searchUserIdDTO); if(searchIdList.isEmpty()) { - resultCode = ProcessResultCode.NOT_FOUND_ACCOUNT.getResultCode(); - message = ProcessResultCode.NOT_FOUND_ACCOUNT.getMessage(); + resultCode = ErrorCode.NOT_FOUND_ACCOUNT.getCode(); + message = ErrorCode.NOT_FOUND_ACCOUNT.getMessage(); } else { - resultCode = ProcessResultCode.FOUND_ACCOUNT.getResultCode(); - message = ProcessResultCode.FOUND_ACCOUNT.getMessage(); + resultCode = ErrorCode.FOUND_ACCOUNT.getCode(); + message = ErrorCode.FOUND_ACCOUNT.getMessage(); userIdList = searchIdList; } @@ -97,21 +90,12 @@ public ApiResponse updatePasswordProcess(LoginDTO loginDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(loginDTO.getApiSecretKey())) { - throw new LoginException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new LoginException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 - if ( - inputValidate.isEmpty(loginDTO.getUserId()) - || inputValidate.isEmpty(loginDTO.getPassword()) - ) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + if (inputValidate.isEmpty(loginDTO.getUserId()) || inputValidate.isEmpty(loginDTO.getPassword())) { + throw new LoginException(ErrorCode.EMPTY_DATA); } // 3. 비밀번호 암호화 @@ -120,13 +104,10 @@ public ApiResponse updatePasswordProcess(LoginDTO loginDTO) { // 4. 비밀번호 update int updatePassword = this.updatePassword(loginDTO); if(updatePassword > 0) { - resultCode = ProcessResultCode.SUCCESS_UPDATE_PASSWORD.getResultCode(); - message = ProcessResultCode.SUCCESS_UPDATE_PASSWORD.getMessage(); + resultCode = ErrorCode.SUCCESS_UPDATE_PASSWORD.getCode(); + message = ErrorCode.SUCCESS_UPDATE_PASSWORD.getMessage(); } else { - throw new LoginException( - ProcessResultCode.FAIL_UPDATE_PASSWORD.getResultCode(), - ProcessResultCode.FAIL_UPDATE_PASSWORD.getMessage() - ); + throw new LoginException(ErrorCode.FAIL_UPDATE_PASSWORD); } return ApiResponse.result(resultCode, message); @@ -140,10 +121,7 @@ public ApiResponse login(LoginDTO loginDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(loginDTO.getApiSecretKey())) { - throw new LoginException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new LoginException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 @@ -152,28 +130,19 @@ public ApiResponse login(LoginDTO loginDTO) { || inputValidate.isEmpty(loginDTO.getPassword()) ) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new LoginException(ErrorCode.EMPTY_DATA); } // 3. 아이디로 회원정보 조회 Member member = memberService.selectUserMembershipInfo(loginDTO.getUserId()); if (null == member) { - throw new LoginException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - ); + throw new LoginException(ErrorCode.NOT_FOUND_USER_INFO); } // 4. 비밀번호 일치 여부 boolean ok = passwordEncoder.matches(loginDTO.getPassword(), member.getPassword()); if(!ok) { - throw new LoginException( - ProcessResultCode.FAIL_LOGIN.getResultCode(), - ProcessResultCode.FAIL_LOGIN.getMessage() - ); + throw new LoginException(ErrorCode.FAIL_LOGIN); } // 5. ACCESS, REFRESH TOKEN 생성 & REFRESH TOKEN 저장 @@ -202,10 +171,7 @@ public ApiResponse refreshToken(RefreshTokenRequestDTO refreshTokenRequestDTO) { // 1. 필수 입력값 확인 if (inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new LoginException(ErrorCode.EMPTY_DATA); } // 2. ACCESS, REFRESH TOKEN 재생성 @@ -215,7 +181,7 @@ public ApiResponse refreshToken(RefreshTokenRequestDTO refreshTokenRequestDTO) { message = pair.message(); if(!resultCode.equals("200")) { - throw new LoginException(resultCode, message); + return ApiResponse.error(resultCode, message); } data.put("accessToken", pair.accessToken()); @@ -230,19 +196,13 @@ public ApiResponse logout(RefreshTokenRequestDTO refreshTokenRequestDTO) { // 1. 필수 입력값 확인 if(inputValidate.isEmpty(refreshTokenRequestDTO.getRefreshToken())) { - throw new LoginException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new LoginException(ErrorCode.EMPTY_DATA); } // 2. 로그아웃 authService.logout(refreshTokenRequestDTO.getRefreshToken()); // DB REVOKE - return ApiResponse.result( - ProcessResultCode.SUCCESS_LOGOUT.getResultCode(), - ProcessResultCode.SUCCESS_LOGOUT.getMessage() - ); + return ApiResponse.result(ErrorCode.SUCCESS_LOGOUT); } public int selectMemberCnt(LoginDTO loginDTO){ diff --git a/src/main/java/com/barogagi/region/controller/RegionController.java b/src/main/java/com/barogagi/region/controller/RegionController.java index e971090..d7b6889 100644 --- a/src/main/java/com/barogagi/region/controller/RegionController.java +++ b/src/main/java/com/barogagi/region/controller/RegionController.java @@ -93,7 +93,7 @@ public ApiResponse getGeocode(@Parameter(description = "법정동/행정동의 throw new RuntimeException(e); } finally { - apiResponse.setResultCode(resultCode); + apiResponse.setCode(resultCode); apiResponse.setMessage(message); } return apiResponse; diff --git a/src/main/java/com/barogagi/response/ApiResponse.java b/src/main/java/com/barogagi/response/ApiResponse.java index 9b773b2..8787b49 100644 --- a/src/main/java/com/barogagi/response/ApiResponse.java +++ b/src/main/java/com/barogagi/response/ApiResponse.java @@ -1,14 +1,16 @@ package com.barogagi.response; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; import lombok.Setter; +import org.springframework.http.HttpStatus; @Getter @Setter public class ApiResponse { // 결과 코드 - private String resultCode; + private String code; // 결과 메시지 private String message; @@ -18,7 +20,7 @@ public class ApiResponse { public static ApiResponse success(T data, String message) { ApiResponse res = new ApiResponse<>(); - res.resultCode = "SUCCESS"; + res.code = "SUCCESS"; res.message = message; res.data = data; return res; @@ -26,14 +28,14 @@ public static ApiResponse success(T data, String message) { public static ApiResponse error(String code, String message) { ApiResponse res = new ApiResponse<>(); - res.resultCode = code; + res.code = code; res.message = message; return res; } public static ApiResponse resultData(T data, String code, String message) { ApiResponse res = new ApiResponse<>(); - res.resultCode = code; + res.code = code; res.message = message; res.data = data; return res; @@ -41,8 +43,15 @@ public static ApiResponse resultData(T data, String code, String message) public static ApiResponse result(String code, String message) { ApiResponse res = new ApiResponse<>(); - res.resultCode = code; + res.code = code; res.message = message; return res; } + + public static ApiResponse result(ErrorCode errorCode) { + ApiResponse res = new ApiResponse<>(); + res.code = errorCode.getCode(); + res.message = errorCode.getMessage(); + return res; + } } diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 129ad0a..6d9d037 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -66,23 +66,17 @@ public ApiResponse getScheduleList(HttpServletRequest request) { Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); if (!"200".equals(resultCode)) { - throw new BasicException( - resultCode, - String.valueOf(resultMap.get("message")) - ); + return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); //if(userIdCheckVO.getApiSecretKey().equals(API_SECRET_KEY)){ result = scheduleQueryService.getScheduleList(membershipNo); - } catch (BasicException e) { - return ApiResponse.error(e.getResultCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 목록 조회 실패"); } - return ApiResponse.success(result, "일정 목록 조회 성공"); } @@ -99,10 +93,7 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); if (!"200".equals(resultCode)) { - throw new BasicException( - resultCode, - String.valueOf(resultMap.get("message")) - ); + return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); @@ -199,7 +190,7 @@ public ApiResponse createSchedule( result = scheduleCommandService.createSchedule(scheduleRegistReqDTO); } catch (BasicException e) { - return ApiResponse.error(e.getResultCode(), e.getMessage()); + return ApiResponse.error(e.getCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 생성 실패"); } @@ -373,17 +364,12 @@ public ApiResponse saveSchedule( Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); if (!"200".equals(resultCode)) { - throw new BasicException( - resultCode, - String.valueOf(resultMap.get("message")) - ); + return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); result = scheduleCommandService.saveSchedule(scheduleRegistResDTO, membershipNo); - } catch (BasicException e) { - return ApiResponse.error(e.getResultCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 저장 실패"); } @@ -481,7 +467,7 @@ public ApiResponse updateSchedule( result = scheduleCommandService.updateSchedule(scheduleRegistResDTO); } catch (BasicException e) { - return ApiResponse.error(e.getResultCode(), e.getMessage()); + return ApiResponse.error(e.getCode(), e.getMessage()); } catch (Exception e) { return ApiResponse.error("404", "일정 저장 실패"); } @@ -503,7 +489,7 @@ public ApiResponse deleteSchedule(@Parameter(description = "삭제할 일정 번 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); if (!"200".equals(resultCode)) { - throw new BasicException( resultCode,String.valueOf(resultMap.get("message"))); + return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); diff --git a/src/main/java/com/barogagi/terms/exception/TermsException.java b/src/main/java/com/barogagi/terms/exception/TermsException.java index 9ecb1a7..ed4fc81 100644 --- a/src/main/java/com/barogagi/terms/exception/TermsException.java +++ b/src/main/java/com/barogagi/terms/exception/TermsException.java @@ -1,12 +1,13 @@ package com.barogagi.terms.exception; import com.barogagi.config.exception.BusinessException; +import com.barogagi.util.exception.ErrorCode; import lombok.Getter; @Getter public class TermsException extends BusinessException { - public TermsException(String resultCode, String message) { - super(resultCode, message); + public TermsException(ErrorCode errorCode) { + super(errorCode); } } diff --git a/src/main/java/com/barogagi/terms/service/TermsService.java b/src/main/java/com/barogagi/terms/service/TermsService.java index 3e16b0f..169ce78 100644 --- a/src/main/java/com/barogagi/terms/service/TermsService.java +++ b/src/main/java/com/barogagi/terms/service/TermsService.java @@ -1,6 +1,5 @@ package com.barogagi.terms.service; -import com.barogagi.config.resultCode.ProcessResultCode; import com.barogagi.member.login.dto.LoginVO; import com.barogagi.member.login.service.LoginService; import com.barogagi.response.ApiResponse; @@ -8,8 +7,8 @@ import com.barogagi.terms.exception.TermsException; import com.barogagi.terms.mapper.TermsMapper; import com.barogagi.util.InputValidate; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.Validator; +import com.barogagi.util.exception.ErrorCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -44,18 +43,12 @@ public ApiResponse termsListProcess(String apiSecretKey, String termsType) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(apiSecretKey)) { - throw new TermsException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new TermsException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(termsType)) { - throw new TermsException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new TermsException(ErrorCode.EMPTY_DATA); } // 3. 약관 조회 @@ -64,17 +57,14 @@ public ApiResponse termsListProcess(String apiSecretKey, String termsType) { List termsList = this.selectTermsList(termsInputDTO); if(termsList.isEmpty()) { - throw new TermsException( - ProcessResultCode.NOT_FOUND_TERMS.getResultCode(), - ProcessResultCode.NOT_FOUND_TERMS.getMessage() - ); + throw new TermsException(ErrorCode.NOT_FOUND_TERMS); } return ApiResponse.resultData( termsList, - ProcessResultCode.FOUND_TERMS.getResultCode(), - ProcessResultCode.FOUND_TERMS.getMessage() + ErrorCode.FOUND_TERMS.getCode(), + ErrorCode.FOUND_TERMS.getMessage() ); } @@ -82,30 +72,21 @@ public ApiResponse termsAgreementsProcess(TermsDTO termsDTO) { // 1. API SECRET KEY 일치 여부 확인 if(!validator.apiSecretKeyCheck(termsDTO.getApiSecretKey())) { - throw new TermsException( - ResultCode.NOT_EQUAL_API_SECRET_KEY.getResultCode(), - ResultCode.NOT_EQUAL_API_SECRET_KEY.getMessage() - ); + throw new TermsException(ErrorCode.NOT_EQUAL_API_SECRET_KEY); } // 2. 필수 입력값 확인 if(inputValidate.isEmpty(termsDTO.getUserId()) || termsDTO.getTermsAgreeList() == null || termsDTO.getTermsAgreeList().isEmpty()) { - throw new TermsException( - ProcessResultCode.EMPTY_DATA.getResultCode(), - ProcessResultCode.EMPTY_DATA.getMessage() - ); + throw new TermsException(ErrorCode.EMPTY_DATA); } LoginVO lvo = new LoginVO(); lvo.setUserId(termsDTO.getUserId()); LoginVO loginVO = loginService.findMembershipNo(lvo); if(null == loginVO) { - throw new TermsException( - ProcessResultCode.NOT_FOUND_USER_INFO.getResultCode(), - ProcessResultCode.NOT_FOUND_USER_INFO.getMessage() - ); + throw new TermsException(ErrorCode.NOT_FOUND_USER_INFO); } List termsAgreeDTOList = new ArrayList<>(); @@ -121,15 +102,12 @@ public ApiResponse termsAgreementsProcess(TermsDTO termsDTO) { String resCode = this.insertTermsAgreeList(termsAgreeDTOList); if(!resCode.equals("200")) { - throw new TermsException( - ProcessResultCode.FAIL_INSERT_TERMS.getResultCode(), - ProcessResultCode.FAIL_INSERT_TERMS.getMessage() - ); + throw new TermsException(ErrorCode.FAIL_INSERT_TERMS); } return ApiResponse.result( - ProcessResultCode.SUCCESS_INSERT_TERMS.getResultCode(), - ProcessResultCode.SUCCESS_INSERT_TERMS.getMessage() + ErrorCode.SUCCESS_INSERT_TERMS.getCode(), + ErrorCode.SUCCESS_INSERT_TERMS.getMessage() ); } diff --git a/src/main/java/com/barogagi/util/MembershipUtil.java b/src/main/java/com/barogagi/util/MembershipUtil.java index e9b4ebf..92c1959 100644 --- a/src/main/java/com/barogagi/util/MembershipUtil.java +++ b/src/main/java/com/barogagi/util/MembershipUtil.java @@ -1,7 +1,7 @@ package com.barogagi.util; -import com.barogagi.config.resultCode.ResultCode; import com.barogagi.util.exception.BasicException; +import com.barogagi.util.exception.ErrorCode; import jakarta.servlet.http.HttpServletRequest; import org.springframework.stereotype.Component; @@ -22,18 +22,15 @@ public Map membershipNoService(HttpServletRequest request) { try { Object membershipNoAttr = request.getAttribute("membershipNo"); if(membershipNoAttr == null) { - throw new BasicException( - ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), - ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() - ); + throw new BasicException(ErrorCode.NOT_EXIST_ACCESS_AUTH); } membershipNo = String.valueOf(membershipNoAttr); - resultCode = ResultCode.EXIST_ACCESS_AUTH.getResultCode(); - message = ResultCode.EXIST_ACCESS_AUTH.getMessage(); + resultCode = ErrorCode.EXIST_ACCESS_AUTH.getCode(); + message = ErrorCode.EXIST_ACCESS_AUTH.getMessage(); } catch (BasicException ex) { - resultCode = ex.getResultCode(); + resultCode = ex.getCode(); message = ex.getMessage(); } finally { resultMap.put("resultCode", resultCode); @@ -48,10 +45,7 @@ public String selectMembershipNo(HttpServletRequest request) { Object membershipNoAttr = request.getAttribute("membershipNo"); if(null == membershipNoAttr) { - throw new BasicException( - ResultCode.NOT_EXIST_ACCESS_AUTH.getResultCode(), - ResultCode.NOT_EXIST_ACCESS_AUTH.getMessage() - ); + throw new BasicException(ErrorCode.NOT_EXIST_ACCESS_AUTH); } return String.valueOf(membershipNoAttr); diff --git a/src/main/java/com/barogagi/util/exception/BasicException.java b/src/main/java/com/barogagi/util/exception/BasicException.java index 814c567..196f0c6 100644 --- a/src/main/java/com/barogagi/util/exception/BasicException.java +++ b/src/main/java/com/barogagi/util/exception/BasicException.java @@ -6,20 +6,11 @@ @Getter public class BasicException extends BusinessException { - private final String resultCode; private final ErrorCode errorCode; // 신규 생성자 public BasicException(ErrorCode errorCode) { - super(errorCode.getCode(), errorCode.getMessage()); + super(errorCode); this.errorCode = errorCode; - this.resultCode = errorCode.getCode(); - } - - // 기존 생성자 - public BasicException(String resultCode, String message) { - super(resultCode, message); - this.resultCode = resultCode; - errorCode = null; } } diff --git a/src/main/java/com/barogagi/util/exception/ErrorCode.java b/src/main/java/com/barogagi/util/exception/ErrorCode.java index 9e365af..e07ea93 100644 --- a/src/main/java/com/barogagi/util/exception/ErrorCode.java +++ b/src/main/java/com/barogagi/util/exception/ErrorCode.java @@ -8,9 +8,18 @@ @RequiredArgsConstructor public enum ErrorCode { + // API_SECRET_KEY 일치 X + NOT_EQUAL_API_SECRET_KEY(HttpStatus.UNAUTHORIZED, "A100", "잘못된 접근입니다."), + + // ACCESS TOKEN + NOT_EXIST_ACCESS_AUTH(HttpStatus.UNAUTHORIZED, "A401", "접근 권한이 존재하지 않습니다."), + EXIST_ACCESS_AUTH(HttpStatus.OK, "A200", "회원 번호가 존재합니다."), + EXPIRE_TOKEN(HttpStatus.UNAUTHORIZED, "A300", "Token이 만료되었습니다."), + // Common INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON-500", "서버 오류가 발생했습니다."), INVALID_REQUEST(HttpStatus.BAD_REQUEST, "COMMON-400", "잘못된 요청입니다."), + EMPTY_DATA(HttpStatus.BAD_REQUEST, "C101", "정보를 입력해주세요."), // Membership MEMBERSHIP_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBERSHIP-404", "멤버십 정보가 없습니다."), @@ -28,8 +37,77 @@ public enum ErrorCode { ITEM_NOT_FOUND(HttpStatus.NOT_FOUND, "ITEM-001", "아이템 정보를 찾을 수 없습니다."), // Region - REGION_NOT_FOUND(HttpStatus.NOT_FOUND, "REGION-001", "지역 정보를 찾을 수 없습니다."); + REGION_NOT_FOUND(HttpStatus.NOT_FOUND, "REGION-001", "지역 정보를 찾을 수 없습니다."), + + // Nickname + INVALID_NICKNAME(HttpStatus.BAD_REQUEST, "N102", "적합하지 않는 닉네임입니다."), + UNAVAILABLE_NICKNAME(HttpStatus.CONFLICT, "N103", "해당 닉네임 사용이 불가능합니다."), + AVAILABLE_NICKNAME(HttpStatus.OK, "N200", "사용 가능한 닉네임입니다."), + + // UserId + INVALID_USER_ID(HttpStatus.BAD_REQUEST, "U102", "적합한 아이디가 아닙니다."), + UNAVAILABLE_USER_ID(HttpStatus.CONFLICT, "U300", "해당 아이디 사용이 불가능합니다."), + AVAILABLE_USER_ID(HttpStatus.OK, "U200", "해당 아이디 사용이 가능합니다."), + + // SignUp + INVALID_SIGN_UP(HttpStatus.BAD_REQUEST, "S102", "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), + SUCCESS_SIGN_UP(HttpStatus.CREATED, "S200", "회원가입에 성공하였습니다."), + FAIL_SIGN_UP(HttpStatus.INTERNAL_SERVER_ERROR, "S300", "회원가입에 실패하였습니다."), + + // DeleteAccount + SUCCESS_DELETE_ACCOUNT(HttpStatus.OK, "D200", "회원 탈퇴되었습니다."), + FAIL_DELETE_ACCOUNT(HttpStatus.INTERNAL_SERVER_ERROR, "D300", "회원 탈퇴 실패하였습니다."), + + // FindUser + FOUND_ACCOUNT(HttpStatus.OK, "F200", "해당 전화번호로 가입된 아이디가 존재합니다."), + NOT_FOUND_ACCOUNT(HttpStatus.NOT_FOUND, "F201", "해당 전화번호로 가입된 계정이 존재하지 않습니다."), + + // UpdatePassword + SUCCESS_UPDATE_PASSWORD(HttpStatus.OK, "U200", "비밀번호 재설정에 성공하였습니다."), + FAIL_UPDATE_PASSWORD(HttpStatus.INTERNAL_SERVER_ERROR, "U300", "비밀번호 재설정에 실패하였습니다."), + + // Login + NOT_FOUND_USER_INFO(HttpStatus.NOT_FOUND, "L102", "회원 정보가 존재하지 않습니다."), + FAIL_LOGIN(HttpStatus.UNAUTHORIZED, "L103", "로그인에 실패하였습니다."), + SUCCESS_LOGIN(HttpStatus.OK, "L200", "로그인에 성공하였습니다."), + + // RefreshToken + REQUIRED_LOGIN(HttpStatus.UNAUTHORIZED, "R110", "로그인을 진행해주세요."), + REQUIRED_RE_LOGIN(HttpStatus.UNAUTHORIZED, "R120", "로그인을 다시 진행해주세요."), + SUCCESS_REFRESH_TOKEN(HttpStatus.OK, "R200", "토큰이 발급되었습니다."), + FAIL_REFRESH_TOKEN(HttpStatus.INTERNAL_SERVER_ERROR, "R130", "토큰 발급에 실패하였습니다."), + UNAVAILABLE_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "R301", "유효하지 않은 refresh token입니다."), + NOT_FOUND_AVAILABLE_REFRESH_TOKEN(HttpStatus.NOT_FOUND, "R302", "유효한 token 정보를 찾을 수 없습니다."), + + // Logout + FAIL_LOGOUT(HttpStatus.INTERNAL_SERVER_ERROR, "L300", "로그아웃 실패하였습니다."), + SUCCESS_LOGOUT(HttpStatus.OK, "L200", "로그아웃 되었습니다."), + + // Terms + FOUND_TERMS(HttpStatus.OK, "T200", "약관 조회에 성공하였습니다."), + NOT_FOUND_TERMS(HttpStatus.NOT_FOUND, "T102", "약관이 존재하지 않습니다."), + SUCCESS_INSERT_TERMS(HttpStatus.CREATED, "T200", "약관 저장에 성공하였습니다."), + FAIL_INSERT_TERMS(HttpStatus.INTERNAL_SERVER_ERROR, "T300", "약관 저장에 실패하였습니다."), + + // MemberInfo + FOUND_USER_INFO(HttpStatus.OK, "M200", "회원 정보 조회가 완료되었습니다."), + FAIL_UPDATE_USER_INFO(HttpStatus.INTERNAL_SERVER_ERROR, "M404", "사용자 정보 수정 실패하였습니다."), + SUCCESS_UPDATE_USER_INFO(HttpStatus.OK, "M200", "사용자 정보 수정 완료하였습니다."), + + // MainPage + NOT_FOUND_SCHEDULE(HttpStatus.NOT_FOUND, "M201", "일정이 존재하지 않습니다."), + FOUND_SCHEDULE(HttpStatus.OK, "M200", "조회 성공하였습니다."), + NOT_FOUND_POPULAR_TAG(HttpStatus.NOT_FOUND, "M201", "인기 태그 목록이 존재하지 않습니다."), + FOUND_POPULAR_TAG(HttpStatus.OK, "M200", "인기 태그 조회 완료하였습니다."), + NOT_FOUND_POPULAR_REGION(HttpStatus.NOT_FOUND, "M201", "인기 지역 목록이 존재하지 않습니다."), + FOUND_POPULAR_REGION(HttpStatus.OK, "M200", "인기 지역 조회 완료하였습니다."), + // Approval + SUCCESS_SEND_SMS(HttpStatus.OK, "A200", "인증번호 발송에 성공하었습니다."), + FAIL_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A103", "인증번호 발송에 실패하였습니다."), + ERROR_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A102", "오류가 발생하였습니다."), + SUCCESS_CHECK_SMS(HttpStatus.OK, "A200", "인증이 완료되었습니다."), + FAIL_CHECK_SMS(HttpStatus.BAD_REQUEST, "A300", "인증에 실패하였습니다."); private final HttpStatus status; private final String code; diff --git a/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java index db6f7ea..8bd8a60 100644 --- a/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/barogagi/util/exception/GlobalExceptionHandler.java @@ -35,6 +35,6 @@ public ResponseEntity> handleUnknown(Exception e) { @ExceptionHandler(BusinessException.class) public ApiResponse handleBusinessException(BusinessException e) { - return ApiResponse.result(e.getResultCode(), e.getMessage()); + return ApiResponse.result(e.getCode(), e.getMessage()); } } From dfa656cc17b452a1aa7ec80d9d1f3c460e6f39ef Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 29 Dec 2025 21:22:45 +0900 Subject: [PATCH 59/79] =?UTF-8?q?[FIX]=20:=20=EC=95=84=EC=9D=B4=EB=94=94?= =?UTF-8?q?=20=EC=B0=BE=EA=B8=B0=20api=20=EC=88=98=EC=A0=95=20(#45)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 전화번호에 숫자만 존재하도록 코드 추가 --- .../java/com/barogagi/member/login/service/LoginService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/barogagi/member/login/service/LoginService.java b/src/main/java/com/barogagi/member/login/service/LoginService.java index 7dd60fe..42e4d00 100644 --- a/src/main/java/com/barogagi/member/login/service/LoginService.java +++ b/src/main/java/com/barogagi/member/login/service/LoginService.java @@ -69,6 +69,8 @@ public ApiResponse findUser(SearchUserIdDTO searchUserIdDTO) { throw new LoginException(ErrorCode.EMPTY_DATA); } + searchUserIdDTO.setTel(searchUserIdDTO.getTel().replaceAll("[^0-9]", "")); + searchUserIdDTO.setTel(encryptUtil.encrypt(searchUserIdDTO.getTel())); List searchIdList = this.myUserIdList(searchUserIdDTO); From 215c775918cbc11b7b10cad53ac2c1f60b52163b Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 29 Dec 2025 21:24:11 +0900 Subject: [PATCH 60/79] =?UTF-8?q?[FIX]=20=EC=95=84=EC=9D=B4=EB=94=94=20?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20api=20=EC=88=98=EC=A0=95=20(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * FEAT : 로그 공통화 & 코드 리팩토링 (#39) * [FEAT] : 로그 공통화 작업중 & 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 일반 로그인 & 토큰 재발급 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 약관 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 회원정보 코드 리팩토링 중 * [FEAT] : 로그 공통화 작업중 & 회원정보 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 메인 화면 api 코드 리팩토링 * [FEAT] : 로그 공통화 작업중 & 인증 api 코드 리팩토링 * [FEAT] : 필요없는 import 삭제 * [FEAT] : 필요없는 import 삭제 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FEAT] : 로그 공통화 적용 작업 진행중 * [FIX] : 에러 코드 형식 변경 (#41) * [FIX] : 아이디 찾기 api 수정 (#45) - 전화번호에 숫자만 존재하도록 코드 추가 --- .../java/com/barogagi/member/login/service/LoginService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/barogagi/member/login/service/LoginService.java b/src/main/java/com/barogagi/member/login/service/LoginService.java index 7dd60fe..42e4d00 100644 --- a/src/main/java/com/barogagi/member/login/service/LoginService.java +++ b/src/main/java/com/barogagi/member/login/service/LoginService.java @@ -69,6 +69,8 @@ public ApiResponse findUser(SearchUserIdDTO searchUserIdDTO) { throw new LoginException(ErrorCode.EMPTY_DATA); } + searchUserIdDTO.setTel(searchUserIdDTO.getTel().replaceAll("[^0-9]", "")); + searchUserIdDTO.setTel(encryptUtil.encrypt(searchUserIdDTO.getTel())); List searchIdList = this.myUserIdList(searchUserIdDTO); From 808884a2736d42632e2bc10003a779ad0fc9a040 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 29 Dec 2025 21:54:48 +0900 Subject: [PATCH 61/79] =?UTF-8?q?[FIX]=20:=20=EC=95=84=EC=9D=B4=EB=94=94?= =?UTF-8?q?=20=EC=B0=BE=EA=B8=B0=20api=20=EC=88=98=EC=A0=95=20(#47)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 전화번호에 숫자만 존재하도록 코드 추가 --- .../member/login/service/LoginService.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/barogagi/member/login/service/LoginService.java b/src/main/java/com/barogagi/member/login/service/LoginService.java index 42e4d00..24fc04b 100644 --- a/src/main/java/com/barogagi/member/login/service/LoginService.java +++ b/src/main/java/com/barogagi/member/login/service/LoginService.java @@ -26,28 +26,25 @@ public class LoginService { private final Validator validator; private final InputValidate inputValidate; private final EncryptUtil encryptUtil; - private final PasswordConfig passwordConfig; private final MemberService memberService; private final PasswordEncoder passwordEncoder; private final AuthService authService; @Autowired public LoginService( - LoginMapper loginMapper, - Validator validator, - InputValidate inputValidate, - EncryptUtil encryptUtil, - PasswordConfig passwordConfig, - MemberService memberService, - PasswordEncoder passwordEncoder, - AuthService authService - ) + LoginMapper loginMapper, + Validator validator, + InputValidate inputValidate, + EncryptUtil encryptUtil, + MemberService memberService, + PasswordEncoder passwordEncoder, + AuthService authService + ) { this.loginMapper = loginMapper; this.validator = validator; this.inputValidate = inputValidate; this.encryptUtil = encryptUtil; - this.passwordConfig = passwordConfig; this.memberService = memberService; this.passwordEncoder = passwordEncoder; this.authService = authService; @@ -182,7 +179,7 @@ public ApiResponse refreshToken(RefreshTokenRequestDTO refreshTokenRequestDTO) { resultCode = pair.resultCode(); message = pair.message(); - if(!resultCode.equals("200")) { + if(!resultCode.equals("R200")) { return ApiResponse.error(resultCode, message); } From bc3447e673f6c5acbe5c64551ebb358710b09b52 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 29 Dec 2025 21:57:19 +0900 Subject: [PATCH 62/79] =?UTF-8?q?[FIX]=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20api=20=EC=88=98=EC=A0=95=20(#48)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/login/service/LoginService.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/barogagi/member/login/service/LoginService.java b/src/main/java/com/barogagi/member/login/service/LoginService.java index 42e4d00..24fc04b 100644 --- a/src/main/java/com/barogagi/member/login/service/LoginService.java +++ b/src/main/java/com/barogagi/member/login/service/LoginService.java @@ -26,28 +26,25 @@ public class LoginService { private final Validator validator; private final InputValidate inputValidate; private final EncryptUtil encryptUtil; - private final PasswordConfig passwordConfig; private final MemberService memberService; private final PasswordEncoder passwordEncoder; private final AuthService authService; @Autowired public LoginService( - LoginMapper loginMapper, - Validator validator, - InputValidate inputValidate, - EncryptUtil encryptUtil, - PasswordConfig passwordConfig, - MemberService memberService, - PasswordEncoder passwordEncoder, - AuthService authService - ) + LoginMapper loginMapper, + Validator validator, + InputValidate inputValidate, + EncryptUtil encryptUtil, + MemberService memberService, + PasswordEncoder passwordEncoder, + AuthService authService + ) { this.loginMapper = loginMapper; this.validator = validator; this.inputValidate = inputValidate; this.encryptUtil = encryptUtil; - this.passwordConfig = passwordConfig; this.memberService = memberService; this.passwordEncoder = passwordEncoder; this.authService = authService; @@ -182,7 +179,7 @@ public ApiResponse refreshToken(RefreshTokenRequestDTO refreshTokenRequestDTO) { resultCode = pair.resultCode(); message = pair.message(); - if(!resultCode.equals("200")) { + if(!resultCode.equals("R200")) { return ApiResponse.error(resultCode, message); } From b4bb19a5f5df449cb9b9e1576fc86a9b05fbfc28 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:48:37 +0900 Subject: [PATCH 63/79] =?UTF-8?q?[FIX]=20:=20=ED=86=A0=ED=81=B0=EC=9D=84?= =?UTF-8?q?=20=EC=9D=B4=EC=9A=A9=ED=95=98=EC=97=AC=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20return=20?= =?UTF-8?q?=EB=90=98=EB=8A=94=20resultCode=20=EC=88=98=EC=A0=95=20(#49)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/barogagi/mainPage/service/MainPageService.java | 2 +- .../com/barogagi/member/info/service/MemberService.java | 4 ++-- .../barogagi/schedule/controller/ScheduleController.java | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/barogagi/mainPage/service/MainPageService.java b/src/main/java/com/barogagi/mainPage/service/MainPageService.java index 7358c32..4feaa12 100644 --- a/src/main/java/com/barogagi/mainPage/service/MainPageService.java +++ b/src/main/java/com/barogagi/mainPage/service/MainPageService.java @@ -48,7 +48,7 @@ public MainPageResponse selectUserScheduleInfoProcess(HttpServletRequest request // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { + if(!membershipNoInfo.get("resultCode").equals("A200")) { return MainPageResponse.resultData( userInfoResponseDTO, tagList, regionInfo, diff --git a/src/main/java/com/barogagi/member/info/service/MemberService.java b/src/main/java/com/barogagi/member/info/service/MemberService.java index 88845ee..1df0fe4 100644 --- a/src/main/java/com/barogagi/member/info/service/MemberService.java +++ b/src/main/java/com/barogagi/member/info/service/MemberService.java @@ -44,7 +44,7 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { + if(!membershipNoInfo.get("resultCode").equals("A200")) { return ApiResponse.error( String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message")) @@ -78,7 +78,7 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { + if(!membershipNoInfo.get("resultCode").equals("A200")) { return ApiResponse.error( String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message")) diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 6d9d037..5c1c108 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -65,7 +65,7 @@ public ApiResponse getScheduleList(HttpServletRequest request) { // token으로 membershipNo 조회 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); - if (!"200".equals(resultCode)) { + if (!"A200".equals(resultCode)) { return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); @@ -92,7 +92,7 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 // token으로 membershipNo 조회 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); - if (!"200".equals(resultCode)) { + if (!"A200".equals(resultCode)) { return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); @@ -363,7 +363,7 @@ public ApiResponse saveSchedule( // token으로 membershipNo 조회 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); - if (!"200".equals(resultCode)) { + if (!"A200".equals(resultCode)) { return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); @@ -488,7 +488,7 @@ public ApiResponse deleteSchedule(@Parameter(description = "삭제할 일정 번 // token으로 membershipNo 조회 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); - if (!"200".equals(resultCode)) { + if (!"A200".equals(resultCode)) { return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); From b613c814d7b4179ea515a9bb008af055f2f373ff Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Mon, 29 Dec 2025 22:50:21 +0900 Subject: [PATCH 64/79] =?UTF-8?q?[FIX]=20:=20=ED=86=A0=ED=81=B0=EC=9D=84?= =?UTF-8?q?=20=EC=9D=B4=EC=9A=A9=ED=95=98=EC=97=AC=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20return=20?= =?UTF-8?q?=EB=90=98=EB=8A=94=20resultCode=20=EC=88=98=EC=A0=95=20(#50)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [FIX] : 토큰을 이용하여 회원번호 조회 시 return 되는 resultCode 수정 (#50) --- .../com/barogagi/mainPage/service/MainPageService.java | 2 +- .../com/barogagi/member/info/service/MemberService.java | 4 ++-- .../barogagi/schedule/controller/ScheduleController.java | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/barogagi/mainPage/service/MainPageService.java b/src/main/java/com/barogagi/mainPage/service/MainPageService.java index 7358c32..4feaa12 100644 --- a/src/main/java/com/barogagi/mainPage/service/MainPageService.java +++ b/src/main/java/com/barogagi/mainPage/service/MainPageService.java @@ -48,7 +48,7 @@ public MainPageResponse selectUserScheduleInfoProcess(HttpServletRequest request // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { + if(!membershipNoInfo.get("resultCode").equals("A200")) { return MainPageResponse.resultData( userInfoResponseDTO, tagList, regionInfo, diff --git a/src/main/java/com/barogagi/member/info/service/MemberService.java b/src/main/java/com/barogagi/member/info/service/MemberService.java index 88845ee..1df0fe4 100644 --- a/src/main/java/com/barogagi/member/info/service/MemberService.java +++ b/src/main/java/com/barogagi/member/info/service/MemberService.java @@ -44,7 +44,7 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { + if(!membershipNoInfo.get("resultCode").equals("A200")) { return ApiResponse.error( String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message")) @@ -78,7 +78,7 @@ public ApiResponse updateMemberProcess(HttpServletRequest request, MemberRequest // 1. 회원번호 구하기 Map membershipNoInfo = membershipUtil.membershipNoService(request); - if(!membershipNoInfo.get("resultCode").equals("200")) { + if(!membershipNoInfo.get("resultCode").equals("A200")) { return ApiResponse.error( String.valueOf(membershipNoInfo.get("resultCode")), String.valueOf(membershipNoInfo.get("message")) diff --git a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java index 6d9d037..5c1c108 100644 --- a/src/main/java/com/barogagi/schedule/controller/ScheduleController.java +++ b/src/main/java/com/barogagi/schedule/controller/ScheduleController.java @@ -65,7 +65,7 @@ public ApiResponse getScheduleList(HttpServletRequest request) { // token으로 membershipNo 조회 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); - if (!"200".equals(resultCode)) { + if (!"A200".equals(resultCode)) { return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); @@ -92,7 +92,7 @@ public ApiResponse getScheduleDetail(@Parameter(description = "조회할 일정 // token으로 membershipNo 조회 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); - if (!"200".equals(resultCode)) { + if (!"A200".equals(resultCode)) { return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); @@ -363,7 +363,7 @@ public ApiResponse saveSchedule( // token으로 membershipNo 조회 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); - if (!"200".equals(resultCode)) { + if (!"A200".equals(resultCode)) { return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); @@ -488,7 +488,7 @@ public ApiResponse deleteSchedule(@Parameter(description = "삭제할 일정 번 // token으로 membershipNo 조회 Map resultMap = membershipUtil.membershipNoService(request); String resultCode = String.valueOf(resultMap.get("resultCode")); - if (!"200".equals(resultCode)) { + if (!"A200".equals(resultCode)) { return ApiResponse.error(resultCode, String.valueOf(resultMap.get("message"))); } String membershipNo = String.valueOf(resultMap.get("membershipNo")); From 18ad7db8ab5bfa848704ff39f905b552896c2f73 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:24:04 +0900 Subject: [PATCH 65/79] =?UTF-8?q?[FIX]=20=EB=A9=94=EC=9D=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20api=20=EC=BF=BC=EB=A6=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#51)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] : 토큰을 이용하여 회원번호 조회 시 return 되는 resultCode 수정 * [FIX] : 인기 태그 조회 api, 인기 지역 조회 api 쿼리 수정 --- src/main/resources/mapper/MainPageMapper.xml | 33 ++++++++++---------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/resources/mapper/MainPageMapper.xml b/src/main/resources/mapper/MainPageMapper.xml index fa93745..aab5c54 100644 --- a/src/main/resources/mapper/MainPageMapper.xml +++ b/src/main/resources/mapper/MainPageMapper.xml @@ -82,19 +82,19 @@ SELECT TAG_NUM, COUNT(*) AS usage_count FROM ( SELECT st.TAG_NUM - FROM schedule_tag st - JOIN schedule s ON s.SCHEDULE_NUM = st.SCHEDULE_NUM + FROM SCHEDULE_TAG st + JOIN SCHEDULE s ON s.SCHEDULE_NUM = st.SCHEDULE_NUM WHERE s.DEL_YN = 'N' AND s.start_date >= CURDATE() UNION ALL SELECT pt.TAG_NUM - FROM plan_tag pt - JOIN plan p ON p.PLAN_NUM = pt.PLAN_NUM + FROM PLAN_TAG pt + JOIN PLAN p ON p.PLAN_NUM = pt.PLAN_NUM WHERE p.DEL_YN = 'N' ) AS combined GROUP BY TAG_NUM ) tc - JOIN tag ti ON ti.TAG_NUM = tc.TAG_NUM + JOIN TAG ti ON ti.TAG_NUM = tc.TAG_NUM ORDER BY rankNo ASC LIMIT 10 ]]> @@ -103,23 +103,24 @@
From d7cdb39ce81aa67018ee7858ce48a7a7c60fb9f2 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 16:28:46 +0900 Subject: [PATCH 66/79] =?UTF-8?q?[FIX]=20=EB=A9=94=EC=9D=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20api=20=EC=BF=BC=EB=A6=AC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#52)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] : 인기 태그 조회 api, 인기 지역 조회 api 쿼리 수정 --- src/main/resources/mapper/MainPageMapper.xml | 33 ++++++++++---------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/main/resources/mapper/MainPageMapper.xml b/src/main/resources/mapper/MainPageMapper.xml index fa93745..aab5c54 100644 --- a/src/main/resources/mapper/MainPageMapper.xml +++ b/src/main/resources/mapper/MainPageMapper.xml @@ -82,19 +82,19 @@ SELECT TAG_NUM, COUNT(*) AS usage_count FROM ( SELECT st.TAG_NUM - FROM schedule_tag st - JOIN schedule s ON s.SCHEDULE_NUM = st.SCHEDULE_NUM + FROM SCHEDULE_TAG st + JOIN SCHEDULE s ON s.SCHEDULE_NUM = st.SCHEDULE_NUM WHERE s.DEL_YN = 'N' AND s.start_date >= CURDATE() UNION ALL SELECT pt.TAG_NUM - FROM plan_tag pt - JOIN plan p ON p.PLAN_NUM = pt.PLAN_NUM + FROM PLAN_TAG pt + JOIN PLAN p ON p.PLAN_NUM = pt.PLAN_NUM WHERE p.DEL_YN = 'N' ) AS combined GROUP BY TAG_NUM ) tc - JOIN tag ti ON ti.TAG_NUM = tc.TAG_NUM + JOIN TAG ti ON ti.TAG_NUM = tc.TAG_NUM ORDER BY rankNo ASC LIMIT 10 ]]> @@ -103,23 +103,24 @@
From 25a6ce6c8f2312945aff9de63cf7c1b50a6efbd8 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:01:02 +0900 Subject: [PATCH 67/79] =?UTF-8?q?[FIX]=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B0=9C=EC=86=A1=20api=20=EC=88=98=EC=A0=95=20(#5?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] : 토큰을 이용하여 회원번호 조회 시 return 되는 resultCode 수정 * [FIX] : 인증번호 발송 api 수정 - 전화번호 입력 시 전화번호에 숫자만 남기고 다른 문자 삭제 코드 추가 --- .../java/com/barogagi/approval/service/ApprovalService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/barogagi/approval/service/ApprovalService.java b/src/main/java/com/barogagi/approval/service/ApprovalService.java index feb3ed6..7961e48 100644 --- a/src/main/java/com/barogagi/approval/service/ApprovalService.java +++ b/src/main/java/com/barogagi/approval/service/ApprovalService.java @@ -61,6 +61,8 @@ public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { approvalVO.setCompleteYn("N"); approvalVO.setType(approvalSendVO.getType()); + approvalSendVO.setTel(approvalSendVO.getTel().replaceAll("[^0-9]", "")); + // 전화번호 암호화 approvalVO.setTel(encryptUtil.hashEncodeString(approvalSendVO.getTel())); From a8f5fa72c1f74c9faa6ec1feaead8c8ca01857aa Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:03:25 +0900 Subject: [PATCH 68/79] =?UTF-8?q?[FIX]=20:=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B0=9C=EC=86=A1=20api=20=EC=88=98=EC=A0=95=20(#5?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] : 인증번호 발송 api 수정 - 전화번호 입력 시 전화번호에 숫자만 남기고 다른 문자 삭제 코드 추가 --- .../java/com/barogagi/approval/service/ApprovalService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/barogagi/approval/service/ApprovalService.java b/src/main/java/com/barogagi/approval/service/ApprovalService.java index feb3ed6..7961e48 100644 --- a/src/main/java/com/barogagi/approval/service/ApprovalService.java +++ b/src/main/java/com/barogagi/approval/service/ApprovalService.java @@ -61,6 +61,8 @@ public ApiResponse approvalTelSend(ApprovalSendVO approvalSendVO) { approvalVO.setCompleteYn("N"); approvalVO.setType(approvalSendVO.getType()); + approvalSendVO.setTel(approvalSendVO.getTel().replaceAll("[^0-9]", "")); + // 전화번호 암호화 approvalVO.setTel(encryptUtil.hashEncodeString(approvalSendVO.getTel())); From adfe2e2e1199795a2e650ed144a54cb10852f6d3 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:08:54 +0900 Subject: [PATCH 69/79] =?UTF-8?q?[FIX]=20:=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9D=BC=EC=B9=98=20=EC=97=AC=EB=B6=80=20api=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#55)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] : 인증번호 일치 여부 api 수정 - 전화번호 입력 시 전화번호에 숫자만 남기고 다른 문자 삭제 코드 추가 --- src/main/java/com/barogagi/approval/service/ApprovalService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/barogagi/approval/service/ApprovalService.java b/src/main/java/com/barogagi/approval/service/ApprovalService.java index 7961e48..2dcca08 100644 --- a/src/main/java/com/barogagi/approval/service/ApprovalService.java +++ b/src/main/java/com/barogagi/approval/service/ApprovalService.java @@ -110,6 +110,7 @@ public ApiResponse approvalTelCheck(ApprovalCompleteVO approvalCompleteVO) { // 3. 전화번호 암호화 ApprovalVO approvalVO = new ApprovalVO(); + approvalCompleteVO.setTel(approvalCompleteVO.getTel().replaceAll("[^0-9]", "")); approvalVO.setTel(encryptUtil.hashEncodeString(approvalCompleteVO.getTel())); approvalVO.setCompleteYn("N"); approvalVO.setAuthCode(encryptUtil.hashEncodeString(approvalCompleteVO.getAuthCode())); From 7fb82cf944cb6d43ca18369816f61d2e1b1514fb Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:12:29 +0900 Subject: [PATCH 70/79] =?UTF-8?q?[FIX]=20:=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9D=BC=EC=B9=98=20=EC=97=AC=EB=B6=80=20api=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] : 인증번호 일치 여부 api 수정 - 전화번호 입력 시 전화번호에 숫자만 남기고 다른 문자 삭제 코드 추가 --- src/main/java/com/barogagi/approval/service/ApprovalService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/barogagi/approval/service/ApprovalService.java b/src/main/java/com/barogagi/approval/service/ApprovalService.java index 7961e48..2dcca08 100644 --- a/src/main/java/com/barogagi/approval/service/ApprovalService.java +++ b/src/main/java/com/barogagi/approval/service/ApprovalService.java @@ -110,6 +110,7 @@ public ApiResponse approvalTelCheck(ApprovalCompleteVO approvalCompleteVO) { // 3. 전화번호 암호화 ApprovalVO approvalVO = new ApprovalVO(); + approvalCompleteVO.setTel(approvalCompleteVO.getTel().replaceAll("[^0-9]", "")); approvalVO.setTel(encryptUtil.hashEncodeString(approvalCompleteVO.getTel())); approvalVO.setCompleteYn("N"); approvalVO.setAuthCode(encryptUtil.hashEncodeString(approvalCompleteVO.getAuthCode())); From a5c32b615a0fe294910f55e3c843d9dba62bb842 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:32:07 +0900 Subject: [PATCH 71/79] =?UTF-8?q?[FIX]=20:=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B0=9C=EC=86=A1=20api=20(#57)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] : 인증번호 일치 여부 api 수정 - 전화번호 입력 시 전화번호에 숫자만 남기고 다른 문자 삭제 코드 추가 --- .../java/com/barogagi/sendSms/service/SendSmsService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java index 721c51d..f6fa62c 100644 --- a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java +++ b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java @@ -34,6 +34,10 @@ public boolean sendSms(SendSmsVO sendSmsVO){ boolean result = true; + logger.info("@@ API_KEY={}", API_KEY); + logger.info("@@ API_SECRET_KEY={}", API_SECRET_KEY); + logger.info("@@ SEND_TEL={}", SEND_TEL); + DefaultMessageService messageService = NurigoApp.INSTANCE.initialize(API_KEY, API_SECRET_KEY, "https://api.solapi.com"); // Message 패키지가 중복될 경우 net.nurigo.sdk.message.model.Message로 치환하여 주세요 Message message = new Message(); From 7ee0a5806028bd02937bd66cdf3a48323871fa66 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:32:53 +0900 Subject: [PATCH 72/79] =?UTF-8?q?[FIX]=20:=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B0=9C=EC=86=A1=20api=20(#58)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/barogagi/sendSms/service/SendSmsService.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java index 721c51d..f6fa62c 100644 --- a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java +++ b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java @@ -34,6 +34,10 @@ public boolean sendSms(SendSmsVO sendSmsVO){ boolean result = true; + logger.info("@@ API_KEY={}", API_KEY); + logger.info("@@ API_SECRET_KEY={}", API_SECRET_KEY); + logger.info("@@ SEND_TEL={}", SEND_TEL); + DefaultMessageService messageService = NurigoApp.INSTANCE.initialize(API_KEY, API_SECRET_KEY, "https://api.solapi.com"); // Message 패키지가 중복될 경우 net.nurigo.sdk.message.model.Message로 치환하여 주세요 Message message = new Message(); From 30be9b933409ab1f09606f2019a75ba8f5190fb1 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:40:12 +0900 Subject: [PATCH 73/79] =?UTF-8?q?[FIX]=20:=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B0=9C=EC=86=A1=20api=20(#59)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] : 토큰을 이용하여 회원번호 조회 시 return 되는 resultCode 수정 * [FIX] : 인증번호 발송 api 오류 : API-KEY = null 현상 로그를 찍어서 null로 넘어오는지 확인하기 위함 --- .../java/com/barogagi/sendSms/service/SendSmsService.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java index f6fa62c..d531cd2 100644 --- a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java +++ b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java @@ -7,6 +7,7 @@ import net.nurigo.sdk.message.service.DefaultMessageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @@ -19,6 +20,7 @@ public class SendSmsService { private String API_KEY = ""; private String API_SECRET_KEY = ""; + @Autowired public SendSmsService(Environment environment) { this.SEND_TEL = environment.getProperty("send.sms.tel"); this.API_KEY = environment.getProperty("send.sms.api-key"); @@ -34,10 +36,6 @@ public boolean sendSms(SendSmsVO sendSmsVO){ boolean result = true; - logger.info("@@ API_KEY={}", API_KEY); - logger.info("@@ API_SECRET_KEY={}", API_SECRET_KEY); - logger.info("@@ SEND_TEL={}", SEND_TEL); - DefaultMessageService messageService = NurigoApp.INSTANCE.initialize(API_KEY, API_SECRET_KEY, "https://api.solapi.com"); // Message 패키지가 중복될 경우 net.nurigo.sdk.message.model.Message로 치환하여 주세요 Message message = new Message(); From 4e343ba4341e6e357e245d48a6cc6f931156409a Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 17:42:09 +0900 Subject: [PATCH 74/79] =?UTF-8?q?[FIX]=20:=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B0=9C=EC=86=A1=20api=20(#60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/barogagi/sendSms/service/SendSmsService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java index f6fa62c..00f4de0 100644 --- a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java +++ b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java @@ -7,6 +7,7 @@ import net.nurigo.sdk.message.service.DefaultMessageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @@ -19,6 +20,7 @@ public class SendSmsService { private String API_KEY = ""; private String API_SECRET_KEY = ""; + @Autowired public SendSmsService(Environment environment) { this.SEND_TEL = environment.getProperty("send.sms.tel"); this.API_KEY = environment.getProperty("send.sms.api-key"); From 989c73dc672d1946db7c4554b99b1197d1c643ec Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 18:01:15 +0900 Subject: [PATCH 75/79] =?UTF-8?q?[FIX]=20:=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B0=9C=EC=86=A1=20api=20=EC=88=98=EC=A0=95=20(#6?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [FIX] : 인증번호 발송 api 수정 --- .../sendSms/service/SendSmsService.java | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java index d531cd2..e8ae681 100644 --- a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java +++ b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java @@ -7,8 +7,6 @@ import net.nurigo.sdk.message.service.DefaultMessageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @Service @@ -16,17 +14,6 @@ public class SendSmsService { private static final Logger logger = LoggerFactory.getLogger(SendSmsService.class); - private String SEND_TEL = ""; - private String API_KEY = ""; - private String API_SECRET_KEY = ""; - - @Autowired - public SendSmsService(Environment environment) { - this.SEND_TEL = environment.getProperty("send.sms.tel"); - this.API_KEY = environment.getProperty("send.sms.api-key"); - this.API_SECRET_KEY = environment.getProperty("send.sms.api-secret-key"); - } - /** * SMS 발송 * @param sendSmsVO @@ -36,10 +23,14 @@ public boolean sendSms(SendSmsVO sendSmsVO){ boolean result = true; - DefaultMessageService messageService = NurigoApp.INSTANCE.initialize(API_KEY, API_SECRET_KEY, "https://api.solapi.com"); + String sendTel = "01022581349"; + String apiKey = "NCSLFRXVINKTJJRF"; + String apiSecretKey = "FVIG5UM7784HPLYECNCSYF2FVRXVCBWR"; + + DefaultMessageService messageService = NurigoApp.INSTANCE.initialize(apiKey, apiSecretKey, "https://api.solapi.com"); // Message 패키지가 중복될 경우 net.nurigo.sdk.message.model.Message로 치환하여 주세요 Message message = new Message(); - message.setFrom(SEND_TEL); + message.setFrom(sendTel); message.setTo(sendSmsVO.getRecipientTel()); message.setText(sendSmsVO.getMessageContent()); From 580b481c8b3e8ea2a128935d9698dbc654236cb8 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 18:03:43 +0900 Subject: [PATCH 76/79] =?UTF-8?q?[FIX]=20:=20=EC=9D=B8=EC=A6=9D=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B0=9C=EC=86=A1=20api=20=EC=88=98=EC=A0=95=20(#6?= =?UTF-8?q?2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 인증번호 발송 api 수정 --- .../sendSms/service/SendSmsService.java | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java index 00f4de0..e8ae681 100644 --- a/src/main/java/com/barogagi/sendSms/service/SendSmsService.java +++ b/src/main/java/com/barogagi/sendSms/service/SendSmsService.java @@ -7,8 +7,6 @@ import net.nurigo.sdk.message.service.DefaultMessageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; @Service @@ -16,17 +14,6 @@ public class SendSmsService { private static final Logger logger = LoggerFactory.getLogger(SendSmsService.class); - private String SEND_TEL = ""; - private String API_KEY = ""; - private String API_SECRET_KEY = ""; - - @Autowired - public SendSmsService(Environment environment) { - this.SEND_TEL = environment.getProperty("send.sms.tel"); - this.API_KEY = environment.getProperty("send.sms.api-key"); - this.API_SECRET_KEY = environment.getProperty("send.sms.api-secret-key"); - } - /** * SMS 발송 * @param sendSmsVO @@ -36,14 +23,14 @@ public boolean sendSms(SendSmsVO sendSmsVO){ boolean result = true; - logger.info("@@ API_KEY={}", API_KEY); - logger.info("@@ API_SECRET_KEY={}", API_SECRET_KEY); - logger.info("@@ SEND_TEL={}", SEND_TEL); + String sendTel = "01022581349"; + String apiKey = "NCSLFRXVINKTJJRF"; + String apiSecretKey = "FVIG5UM7784HPLYECNCSYF2FVRXVCBWR"; - DefaultMessageService messageService = NurigoApp.INSTANCE.initialize(API_KEY, API_SECRET_KEY, "https://api.solapi.com"); + DefaultMessageService messageService = NurigoApp.INSTANCE.initialize(apiKey, apiSecretKey, "https://api.solapi.com"); // Message 패키지가 중복될 경우 net.nurigo.sdk.message.model.Message로 치환하여 주세요 Message message = new Message(); - message.setFrom(SEND_TEL); + message.setFrom(sendTel); message.setTo(sendSmsVO.getRecipientTel()); message.setText(sendSmsVO.getMessageContent()); From d9c418d090e89cbf02934028e8cd80f97b5ca245 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 19:24:52 +0900 Subject: [PATCH 77/79] =?UTF-8?q?[UPDATE]=20:=20api=20=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=EC=A0=95=20(#63)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [UPDATE] : api 명세서 수정 --- .../controller/ApprovalController.java | 18 ++++---- .../controller/MainPageController.java | 18 ++++---- .../basic/join/controller/JoinController.java | 32 ++++++++------ .../info/controller/InfoController.java | 16 +++---- .../login/controller/LoginController.java | 43 ++++++++++++------- .../terms/controller/TermsController.java | 17 ++++---- .../barogagi/util/exception/ErrorCode.java | 2 +- 7 files changed, 82 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/barogagi/approval/controller/ApprovalController.java b/src/main/java/com/barogagi/approval/controller/ApprovalController.java index 602e481..21b67cb 100644 --- a/src/main/java/com/barogagi/approval/controller/ApprovalController.java +++ b/src/main/java/com/barogagi/approval/controller/ApprovalController.java @@ -23,11 +23,11 @@ public ApprovalController(ApprovalService approvalService){ @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.
회원가입 시 사용할 경우 type 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증번호 발송에 성공하었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "오류가 발생하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "인증번호 발송에 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A200", description = "인증번호 발송에 성공하었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A102", description = "인증문자 발송 중 오류가 발생하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A103", description = "인증번호 발송에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/send") @@ -40,10 +40,10 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { "
authCode에는 인증번호를 넣어주세요." + "
tel에는 전화번호를 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증이 완료되었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "인증이 실패하었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A200", description = "인증이 완료되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A300", description = "인증이 실패하었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/verify") diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index a7db4fb..71f2d21 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -23,9 +23,9 @@ public MainPageController(MainPageService mainPageService) { @Operation(summary = "유저 일정 정보 API", description = "메인 화면 - 다가오는 일정 부분에 해당하는 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "일정이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M201", description = "일정이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "조회 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/me/schedules") @@ -35,9 +35,9 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { @Operation(summary = "인기 태그 조회 API ", description = "메인 화면 - 오늘 많이 생성되는 일정 부분에 해당하는 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인기 태그 조회 완료하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 태그 목록이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "인기 태그 조회 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M201", description = "인기 태그 목록이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/tags/popular") @@ -47,9 +47,9 @@ public ApiResponse selectPopularTagList(@RequestHeader("API-KEY") String apiSecr @Operation(summary = "인기 지역 조회 API ", description = "메인 화면 - 지금 인기많은 핫 플레이스 부분에 해당하는 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인기 지역 조회 완료하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 지역 목록이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "인기 지역 조회 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M201", description = "인기 지역 목록이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/regions/popular") diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index 1a2e02f..c749627 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -37,12 +37,14 @@ public ApiResponse checkUserId(@RequestHeader("API-KEY") String apiSecretKey, @R @Operation(summary = "회원가입 정보 저장 기능", description = "회원가입 정보 저장 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원가입에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 아이디에 대한 회원 정보가 이미 존재합니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원가입에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "S200", description = "회원가입에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "S102", description = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U300", description = "해당 아이디 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N102", description = "적합하지 않는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N103", description = "해당 닉네임 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "S300", description = "회원가입에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping @@ -52,11 +54,11 @@ public ApiResponse signUp(@RequestBody JoinRequestDTO joinRequestDTO) { @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용 가능한 닉네임입니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합하지 않는 닉네임입니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 닉네임 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N200", description = "사용 가능한 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N102", description = "적합하지 않는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N103", description = "해당 닉네임 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/nickname/exists") @@ -66,9 +68,11 @@ public ApiResponse checkDuplicateNickname(@RequestHeader("API-KEY") String apiSe @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R301", description = "유효하지 않은 refresh token입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R302", description = "유효한 token 정보를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "D200", description = "회원 탈퇴되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "D300", description = "회원 탈퇴 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @DeleteMapping("/me") diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index b5e7342..4a9a064 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -21,9 +21,9 @@ public InfoController(MemberService memberService) { @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 정보 조회가 완료되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "회원 정보 조회가 완료되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping @@ -33,11 +33,11 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { @Operation(summary = "회원 정보 수정", description = "회원 정보 조회 수정입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 닉네임 사용이 불가능합니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자 정보 수정 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용자 정보 수정 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N103", description = "해당 닉네임 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M404", description = "사용자 정보 수정 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "사용자 정보 수정 완료하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PatchMapping diff --git a/src/main/java/com/barogagi/member/login/controller/LoginController.java b/src/main/java/com/barogagi/member/login/controller/LoginController.java index c33fa6e..829ca99 100644 --- a/src/main/java/com/barogagi/member/login/controller/LoginController.java +++ b/src/main/java/com/barogagi/member/login/controller/LoginController.java @@ -22,11 +22,11 @@ public LoginController(LoginService loginService){ @Operation(summary = "로그인", description = "로그인 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "로그인에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "로그인에 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R200", description = "로그인에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L103", description = "로그인에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/login") @@ -36,10 +36,10 @@ public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ @Operation(summary = "아이디 찾기 기능", description = "아이디 찾기 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 전화번호로 가입된 아이디가 존재합니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "해당 전화번호로 가입된 계정이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "F200", description = "해당 전화번호로 가입된 아이디가 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "F201", description = "해당 전화번호로 가입된 계정이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/find-user") @@ -49,10 +49,10 @@ public ApiResponse findUser(@RequestBody SearchUserIdDTO searchUserIdRequestDTO) @Operation(summary = "비밀번호 재설정 기능", description = "비밀번호 재설정 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "비밀번호 재설정에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "비밀번호 재설정에 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U200", description = "비밀번호 재설정에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U300", description = "비밀번호 재설정에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/password-reset/confirm") @@ -60,13 +60,26 @@ public ApiResponse updatePassword(@RequestBody LoginDTO vo){ return loginService.updatePasswordProcess(vo); } - @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") + @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R110", description = "로그인을 진행해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R120", description = "로그인을 다시 진행해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R200", description = "토큰이 발급되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/token/refresh") public ApiResponse refresh(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { return loginService.refreshToken(refreshTokenRequestDTO); } - @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") + @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L200", description = "로그아웃 되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/logout") public ApiResponse logout(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { return loginService.logout(refreshTokenRequestDTO); diff --git a/src/main/java/com/barogagi/terms/controller/TermsController.java b/src/main/java/com/barogagi/terms/controller/TermsController.java index 2612f24..2863e82 100644 --- a/src/main/java/com/barogagi/terms/controller/TermsController.java +++ b/src/main/java/com/barogagi/terms/controller/TermsController.java @@ -18,10 +18,10 @@ public class TermsController { @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.
회원가입 시 사용할 경우 termsType 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 조회에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "약관이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "T200", description = "약관 조회에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "T102", description = "약관이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping @@ -31,10 +31,11 @@ public ApiResponse termsList(@RequestHeader("API-KEY") String apiSecretKey, @Req @Operation(summary = "약관 동의 여부 저장", description = "약관 동의 여부 저장 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 저장에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "해당 사용자의 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "약관 저장에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "T200", description = "약관 저장에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "해당 사용자의 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "T300", description = "약관 저장에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/terms-agreements") diff --git a/src/main/java/com/barogagi/util/exception/ErrorCode.java b/src/main/java/com/barogagi/util/exception/ErrorCode.java index e07ea93..565dbd5 100644 --- a/src/main/java/com/barogagi/util/exception/ErrorCode.java +++ b/src/main/java/com/barogagi/util/exception/ErrorCode.java @@ -105,7 +105,7 @@ public enum ErrorCode { // Approval SUCCESS_SEND_SMS(HttpStatus.OK, "A200", "인증번호 발송에 성공하었습니다."), FAIL_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A103", "인증번호 발송에 실패하였습니다."), - ERROR_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A102", "오류가 발생하였습니다."), + ERROR_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A102", "인증문자 발송 중 오류가 발생하였습니다."), SUCCESS_CHECK_SMS(HttpStatus.OK, "A200", "인증이 완료되었습니다."), FAIL_CHECK_SMS(HttpStatus.BAD_REQUEST, "A300", "인증에 실패하였습니다."); From f66bbf6002b52b05f363a4fa8f1f4d1a830642e0 Mon Sep 17 00:00:00 2001 From: dksgyrud1349 <119853577+dksgyrud1349@users.noreply.github.com> Date: Thu, 1 Jan 2026 19:27:32 +0900 Subject: [PATCH 78/79] =?UTF-8?q?[UPDATE]=20api=20=EB=AA=85=EC=84=B8?= =?UTF-8?q?=EC=84=9C=20=EC=88=98=EC=A0=95=20(#64)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swagger response update --- .../controller/ApprovalController.java | 18 ++++---- .../controller/MainPageController.java | 18 ++++---- .../basic/join/controller/JoinController.java | 32 ++++++++------ .../info/controller/InfoController.java | 16 +++---- .../login/controller/LoginController.java | 43 ++++++++++++------- .../terms/controller/TermsController.java | 17 ++++---- .../barogagi/util/exception/ErrorCode.java | 2 +- 7 files changed, 82 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/barogagi/approval/controller/ApprovalController.java b/src/main/java/com/barogagi/approval/controller/ApprovalController.java index 602e481..21b67cb 100644 --- a/src/main/java/com/barogagi/approval/controller/ApprovalController.java +++ b/src/main/java/com/barogagi/approval/controller/ApprovalController.java @@ -23,11 +23,11 @@ public ApprovalController(ApprovalService approvalService){ @Operation(summary = "인증번호 발송", description = "휴대전화번호로 인증번호 발송하는 기능입니다.
회원가입 시 사용할 경우 type 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증번호 발송에 성공하었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "오류가 발생하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "인증번호 발송에 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A200", description = "인증번호 발송에 성공하었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A102", description = "인증문자 발송 중 오류가 발생하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A103", description = "인증번호 발송에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/send") @@ -40,10 +40,10 @@ public ApiResponse approvalTelSend(@RequestBody ApprovalSendVO approvalSendVO) { "
authCode에는 인증번호를 넣어주세요." + "
tel에는 전화번호를 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인증이 완료되었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "인증이 실패하었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A200", description = "인증이 완료되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A300", description = "인증이 실패하었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/verify") diff --git a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java index a7db4fb..71f2d21 100644 --- a/src/main/java/com/barogagi/mainPage/controller/MainPageController.java +++ b/src/main/java/com/barogagi/mainPage/controller/MainPageController.java @@ -23,9 +23,9 @@ public MainPageController(MainPageService mainPageService) { @Operation(summary = "유저 일정 정보 API", description = "메인 화면 - 다가오는 일정 부분에 해당하는 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "일정이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M201", description = "일정이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "조회 성공하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/me/schedules") @@ -35,9 +35,9 @@ public MainPageResponse selectUserScheduleInfo(HttpServletRequest request) { @Operation(summary = "인기 태그 조회 API ", description = "메인 화면 - 오늘 많이 생성되는 일정 부분에 해당하는 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인기 태그 조회 완료하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 태그 목록이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "인기 태그 조회 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M201", description = "인기 태그 목록이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/tags/popular") @@ -47,9 +47,9 @@ public ApiResponse selectPopularTagList(@RequestHeader("API-KEY") String apiSecr @Operation(summary = "인기 지역 조회 API ", description = "메인 화면 - 지금 인기많은 핫 플레이스 부분에 해당하는 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "인기 지역 조회 완료하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "인기 지역 목록이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "인기 지역 조회 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M201", description = "인기 지역 목록이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/regions/popular") diff --git a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java index 1a2e02f..c749627 100644 --- a/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java +++ b/src/main/java/com/barogagi/member/basic/join/controller/JoinController.java @@ -37,12 +37,14 @@ public ApiResponse checkUserId(@RequestHeader("API-KEY") String apiSecretKey, @R @Operation(summary = "회원가입 정보 저장 기능", description = "회원가입 정보 저장 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원가입에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 아이디에 대한 회원 정보가 이미 존재합니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원가입에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "S200", description = "회원가입에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "S102", description = "적합한 아이디, 비밀번호, 닉네임이 아닙니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U300", description = "해당 아이디 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N102", description = "적합하지 않는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N103", description = "해당 닉네임 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "S300", description = "회원가입에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping @@ -52,11 +54,11 @@ public ApiResponse signUp(@RequestBody JoinRequestDTO joinRequestDTO) { @Operation(summary = "닉네임 중복 체크 API", description = "닉네임 중복 체크 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용 가능한 닉네임입니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "적합하지 않는 닉네임입니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 닉네임 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N200", description = "사용 가능한 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N102", description = "적합하지 않는 닉네임입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N103", description = "해당 닉네임 사용이 불가능합니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping("/nickname/exists") @@ -66,9 +68,11 @@ public ApiResponse checkDuplicateNickname(@RequestHeader("API-KEY") String apiSe @Operation(summary = "회원 탈퇴", description = "회원 탈퇴 API", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 탈퇴되었습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "회원 탈퇴 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R301", description = "유효하지 않은 refresh token입니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R302", description = "유효한 token 정보를 찾을 수 없습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "D200", description = "회원 탈퇴되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "D300", description = "회원 탈퇴 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @DeleteMapping("/me") diff --git a/src/main/java/com/barogagi/member/info/controller/InfoController.java b/src/main/java/com/barogagi/member/info/controller/InfoController.java index b5e7342..4a9a064 100644 --- a/src/main/java/com/barogagi/member/info/controller/InfoController.java +++ b/src/main/java/com/barogagi/member/info/controller/InfoController.java @@ -21,9 +21,9 @@ public InfoController(MemberService memberService) { @Operation(summary = "회원 정보 조회", description = "회원 정보 조회 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "회원 정보 조회가 완료되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "회원 정보 조회가 완료되었습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping @@ -33,11 +33,11 @@ public ApiResponse selectMemberInfo(HttpServletRequest request) { @Operation(summary = "회원 정보 수정", description = "회원 정보 조회 수정입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "접근 권한이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "402", description = "해당 사용자에 대한 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "해당 닉네임 사용이 불가능합니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자 정보 수정 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "사용자 정보 수정 완료하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A401", description = "접근 권한이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "N103", description = "해당 닉네임 사용이 불가능합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M404", description = "사용자 정보 수정 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "M200", description = "사용자 정보 수정 완료하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PatchMapping diff --git a/src/main/java/com/barogagi/member/login/controller/LoginController.java b/src/main/java/com/barogagi/member/login/controller/LoginController.java index c33fa6e..829ca99 100644 --- a/src/main/java/com/barogagi/member/login/controller/LoginController.java +++ b/src/main/java/com/barogagi/member/login/controller/LoginController.java @@ -22,11 +22,11 @@ public LoginController(LoginService loginService){ @Operation(summary = "로그인", description = "로그인 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "로그인에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "회원 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "103", description = "로그인에 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R200", description = "로그인에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L103", description = "로그인에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/login") @@ -36,10 +36,10 @@ public ApiResponse basicMemberLogin(@RequestBody LoginDTO loginRequestDTO){ @Operation(summary = "아이디 찾기 기능", description = "아이디 찾기 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "해당 전화번호로 가입된 아이디가 존재합니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "201", description = "해당 전화번호로 가입된 계정이 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "F200", description = "해당 전화번호로 가입된 아이디가 존재합니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "F201", description = "해당 전화번호로 가입된 계정이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/find-user") @@ -49,10 +49,10 @@ public ApiResponse findUser(@RequestBody SearchUserIdDTO searchUserIdRequestDTO) @Operation(summary = "비밀번호 재설정 기능", description = "비밀번호 재설정 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "비밀번호 재설정에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "비밀번호 재설정에 실패하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U200", description = "비밀번호 재설정에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "U300", description = "비밀번호 재설정에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/password-reset/confirm") @@ -60,13 +60,26 @@ public ApiResponse updatePassword(@RequestBody LoginDTO vo){ return loginService.updatePasswordProcess(vo); } - @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급") + @Operation(summary = "토큰 재발급", description = "Access 토큰 만료 시, Refresh 토큰으로 Access 토큰 재발급", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R110", description = "로그인을 진행해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R120", description = "로그인을 다시 진행해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "회원 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "R200", description = "토큰이 발급되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/token/refresh") public ApiResponse refresh(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { return loginService.refreshToken(refreshTokenRequestDTO); } - @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE") + @Operation(summary = "현재 기기 로그아웃", description = "현재 기기 로그아웃: 전달된 refreshToken이 속한 (membershipNo, deviceId)의 VALID 토큰들을 REVOKE", + responses = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L200", description = "로그아웃 되었습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") + }) @PostMapping("/logout") public ApiResponse logout(@RequestBody RefreshTokenRequestDTO refreshTokenRequestDTO) { return loginService.logout(refreshTokenRequestDTO); diff --git a/src/main/java/com/barogagi/terms/controller/TermsController.java b/src/main/java/com/barogagi/terms/controller/TermsController.java index 2612f24..2863e82 100644 --- a/src/main/java/com/barogagi/terms/controller/TermsController.java +++ b/src/main/java/com/barogagi/terms/controller/TermsController.java @@ -18,10 +18,10 @@ public class TermsController { @Operation(summary = "약관 목록 조회", description = "약관 목록 조회 기능입니다.
회원가입 시 사용할 경우 termsType 값을 JOIN-MEMBERSHIP 값으로 넣어주세요.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 조회에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "101", description = "정보를 입력해주세요."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "약관이 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "T200", description = "약관 조회에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "T102", description = "약관이 존재하지 않습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @GetMapping @@ -31,10 +31,11 @@ public ApiResponse termsList(@RequestHeader("API-KEY") String apiSecretKey, @Req @Operation(summary = "약관 동의 여부 저장", description = "약관 동의 여부 저장 기능입니다.", responses = { - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "약관 저장에 성공하였습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "100", description = "API SECRET KEY 불일치"), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "102", description = "해당 사용자의 정보가 존재하지 않습니다."), - @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "300", description = "약관 저장에 실패하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "T200", description = "약관 저장에 성공하였습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "A100", description = "API SECRET KEY 불일치"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "C101", description = "정보를 입력해주세요."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "L102", description = "해당 사용자의 정보가 존재하지 않습니다."), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "T300", description = "약관 저장에 실패하였습니다."), @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "오류가 발생하였습니다.") }) @PostMapping("/terms-agreements") diff --git a/src/main/java/com/barogagi/util/exception/ErrorCode.java b/src/main/java/com/barogagi/util/exception/ErrorCode.java index e07ea93..565dbd5 100644 --- a/src/main/java/com/barogagi/util/exception/ErrorCode.java +++ b/src/main/java/com/barogagi/util/exception/ErrorCode.java @@ -105,7 +105,7 @@ public enum ErrorCode { // Approval SUCCESS_SEND_SMS(HttpStatus.OK, "A200", "인증번호 발송에 성공하었습니다."), FAIL_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A103", "인증번호 발송에 실패하였습니다."), - ERROR_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A102", "오류가 발생하였습니다."), + ERROR_SEND_SMS(HttpStatus.INTERNAL_SERVER_ERROR, "A102", "인증문자 발송 중 오류가 발생하였습니다."), SUCCESS_CHECK_SMS(HttpStatus.OK, "A200", "인증이 완료되었습니다."), FAIL_CHECK_SMS(HttpStatus.BAD_REQUEST, "A300", "인증에 실패하였습니다."); From b1d3091fb01642dcceb7d77294f272a280e4d7c2 Mon Sep 17 00:00:00 2001 From: RohDamin Date: Sat, 10 Jan 2026 14:33:23 +0900 Subject: [PATCH 79/79] =?UTF-8?q?update:=20=EC=8B=A4=EC=84=9C=EB=B2=84=20b?= =?UTF-8?q?lue/green=20=EB=B0=B0=ED=8F=AC=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20deploy-backend.yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-backend.yml | 31 ++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 6c0ee52..6cc0d4a 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -44,7 +44,8 @@ jobs: ./Dockerfile \ ${{ secrets.USERNAME }}@${{ env.HOST }}:${{ env.TARGET_DIR }} - - name: SSH into EC2 and run Docker + - name: SSH into EC2 and run Docker (main + if: github.ref == 'refs/heads/main' run: | ssh -o ServerAliveInterval=30 -i key.pem -o StrictHostKeyChecking=no ${{ secrets.USERNAME }}@${{ env.HOST }} << EOF cd $TARGET_DIR @@ -52,4 +53,30 @@ jobs: sudo docker rm backend || true sudo docker build -t backend . sudo docker run -d -p 8080:8080 --name backend backend - EOF \ No newline at end of file + EOF + - name: SSH into server and deploy (release - blue/green) + if: github.ref == 'refs/heads/release' + run: | + ssh -o ServerAliveInterval=30 -i key.pem -o StrictHostKeyChecking=no \ + ${{ secrets.USERNAME }}@${{ env.HOST }} << 'EOF' + + cd $TARGET_DIR + + # 현재 active 확인 (nginx 기준) + if grep -q "8081" /etc/nginx/sites-available/barogagi.xyz; then + TARGET="blue" + else + TARGET="green" + fi + + echo "Deploy target: $TARGET" + + # 이미지 빌드 + docker build -t barogagi-backend:single . + + # inactive 컨테이너만 재기동 + docker compose up -d backend-$TARGET + + # 트래픽 전환 + /srv/barogagi/backend/scripts/switch.sh + EOF