From e4c4c7e932e2830a06487361ddde4246c48a83bc Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 05:54:31 +0900 Subject: [PATCH 01/23] =?UTF-8?q?feat:=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 398b4c9..341f336 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,7 @@ dependencies { implementation 'org.springframework.data:spring-data-redis' implementation 'org.springframework.boot:spring-boot-starter-mail' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.squareup.okhttp3:okhttp:4.10.0' implementation 'com.google.auth:google-auth-library-oauth2-http:1.17.0' implementation 'com.google.code.gson:gson:2.8.9' From 44c8d89bccef70c99cbf2f954d6215ad3bf9059f Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 05:54:39 +0900 Subject: [PATCH 02/23] =?UTF-8?q?feat:=20dto=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/location/dto/LocationRequestDto.java | 9 +++++++++ .../domain/location/dto/RegionInfoDto.java | 10 ++++++++++ .../domain/location/dto/RegionTemperatureDto.java | 11 +++++++++++ 3 files changed, 30 insertions(+) create mode 100644 src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationRequestDto.java create mode 100644 src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionInfoDto.java create mode 100644 src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionTemperatureDto.java diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationRequestDto.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationRequestDto.java new file mode 100644 index 0000000..8fa3759 --- /dev/null +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationRequestDto.java @@ -0,0 +1,9 @@ +package com.howWeather.howWeather_backend.domain.location.dto; + +import lombok.Data; + +@Data +public class LocationRequestDto { + private double latitude; + private double longitude; +} diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionInfoDto.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionInfoDto.java new file mode 100644 index 0000000..94cc042 --- /dev/null +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionInfoDto.java @@ -0,0 +1,10 @@ +package com.howWeather.howWeather_backend.domain.location.dto; + +import lombok.Data; + +@Data +public class RegionInfoDto { + private String address_name; + private String region_1depth_name; + private String region_2depth_name; +} diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionTemperatureDto.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionTemperatureDto.java new file mode 100644 index 0000000..1572c50 --- /dev/null +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionTemperatureDto.java @@ -0,0 +1,11 @@ +package com.howWeather.howWeather_backend.domain.location.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class RegionTemperatureDto { + private RegionInfoDto regionInfo; + private Double temperature; +} From 7f88ab104b8111382603a64c7d77e43228275457 Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 06:20:16 +0900 Subject: [PATCH 03/23] =?UTF-8?q?feat:=20=EB=82=A0=EC=A7=9C=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(=EC=96=B4=EC=A0=9C=EC=99=80=20=EC=98=A4?= =?UTF-8?q?=EB=8A=98=EB=A7=8C=20=ED=97=88=EC=9A=A9)=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../record_calendar/dto/RecordRequestDto.java | 4 ++-- .../global/custom/YesterdayOrToday.java | 16 ++++++++++++++++ .../global/custom/YesterdayTodayValidator.java | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/howWeather/howWeather_backend/global/custom/YesterdayOrToday.java create mode 100644 src/main/java/com/howWeather/howWeather_backend/global/custom/YesterdayTodayValidator.java diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/record_calendar/dto/RecordRequestDto.java b/src/main/java/com/howWeather/howWeather_backend/domain/record_calendar/dto/RecordRequestDto.java index aa003d2..ea897b0 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/record_calendar/dto/RecordRequestDto.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/record_calendar/dto/RecordRequestDto.java @@ -1,10 +1,10 @@ package com.howWeather.howWeather_backend.domain.record_calendar.dto; import com.howWeather.howWeather_backend.global.custom.ValidateUppersOrOuters; +import com.howWeather.howWeather_backend.global.custom.YesterdayOrToday; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -23,7 +23,7 @@ public class RecordRequestDto { @Min(1) @Max(3) int feeling; - @NotNull + @YesterdayOrToday LocalDate date; List uppers; diff --git a/src/main/java/com/howWeather/howWeather_backend/global/custom/YesterdayOrToday.java b/src/main/java/com/howWeather/howWeather_backend/global/custom/YesterdayOrToday.java new file mode 100644 index 0000000..ed629dc --- /dev/null +++ b/src/main/java/com/howWeather/howWeather_backend/global/custom/YesterdayOrToday.java @@ -0,0 +1,16 @@ +package com.howWeather.howWeather_backend.global.custom; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +@Target({ ElementType.FIELD, ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = YesterdayTodayValidator.class) +@Documented +public @interface YesterdayOrToday { + String message() default "날짜는 어제 또는 오늘이어야 합니다."; + Class[] groups() default {}; + Class[] payload() default {}; +} \ No newline at end of file diff --git a/src/main/java/com/howWeather/howWeather_backend/global/custom/YesterdayTodayValidator.java b/src/main/java/com/howWeather/howWeather_backend/global/custom/YesterdayTodayValidator.java new file mode 100644 index 0000000..7cde576 --- /dev/null +++ b/src/main/java/com/howWeather/howWeather_backend/global/custom/YesterdayTodayValidator.java @@ -0,0 +1,16 @@ +package com.howWeather.howWeather_backend.global.custom; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.time.LocalDate; + +public class YesterdayTodayValidator implements ConstraintValidator { + @Override + public boolean isValid(LocalDate value, ConstraintValidatorContext context) { + if (value == null) return true; + LocalDate yesterday = LocalDate.now().minusDays(1); + LocalDate today = LocalDate.now(); + return !value.isBefore(yesterday) && !value.isAfter(today); + } +} From 4b72818452aff884dda3b290f0425855907e127f Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 06:20:46 +0900 Subject: [PATCH 04/23] =?UTF-8?q?feat:=20=EC=9C=84=EA=B2=BD=EB=8F=84=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EB=B0=9B=EC=95=84=20?= =?UTF-8?q?=EC=A7=80=EC=97=AD=EB=AA=85=20=EB=B0=8F=20=EC=98=A8=EB=8F=84=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20dto=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../location/dto/LocationRequestDto.java | 9 ------- .../dto/LocationWeatherRequestDto.java | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) delete mode 100644 src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationRequestDto.java create mode 100644 src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationWeatherRequestDto.java diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationRequestDto.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationRequestDto.java deleted file mode 100644 index 8fa3759..0000000 --- a/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationRequestDto.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.howWeather.howWeather_backend.domain.location.dto; - -import lombok.Data; - -@Data -public class LocationRequestDto { - private double latitude; - private double longitude; -} diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationWeatherRequestDto.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationWeatherRequestDto.java new file mode 100644 index 0000000..57cb562 --- /dev/null +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/LocationWeatherRequestDto.java @@ -0,0 +1,25 @@ +package com.howWeather.howWeather_backend.domain.location.dto; + +import com.howWeather.howWeather_backend.global.custom.YesterdayOrToday; +import jakarta.validation.constraints.*; +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class LocationWeatherRequestDto { + @DecimalMin(value = "-90.0", message = "latitude 최소값은 -90입니다.") + @DecimalMax(value = "90.0", message = "latitude 최대값은 90입니다.") + private double latitude; + + @DecimalMin(value = "-180.0", message = "longitude 최소값은 -180입니다.") + @DecimalMax(value = "180.0", message = "longitude 최대값은 180입니다.") + private double longitude; + + @Min(value = 1, message = "timeSlot은 1 이상이어야 합니다.") + @Max(value = 3, message = "timeSlot은 3 이하여야 합니다.") + private int timeSlot; + + @YesterdayOrToday + private LocalDate date; +} From 4756897a7180b86ebecceaf9131c79dd9696daab Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 06:23:28 +0900 Subject: [PATCH 05/23] =?UTF-8?q?chore:=20=EC=B9=B4=EC=B9=B4=EC=98=A4=20?= =?UTF-8?q?=EC=A7=80=EC=98=A4=EC=BD=94=EB=94=A9=20api=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=BD=94=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 --- .../howWeather_backend/global/exception/ErrorCode.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java b/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java index 5737fb1..bef7a2f 100644 --- a/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java +++ b/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java @@ -77,6 +77,9 @@ public enum ErrorCode { GOOGLE_INVALID_ACCESS_TOKEN("유효하지 않은 구글 액세스 토큰입니다.", HttpStatus.UNAUTHORIZED), NO_OAUTH2_CLIENT("Auth2AuthorizedClient가 존재하지 않습니다.", HttpStatus.UNAUTHORIZED), OAUTH2_TOKEN_NOT_FOUND("OAuth2 access token을 찾을 수 없습니다.", HttpStatus.UNAUTHORIZED), + + // 지역 + KAKAO_API_REGION_NOT_FOUND("해당 지역을 찾는 데 실패했습니다.", HttpStatus.NOT_FOUND) ; private final String message; From 840e015ee1a7c5a3740024f6f9cdb20aa64bed81 Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 06:23:42 +0900 Subject: [PATCH 06/23] =?UTF-8?q?feat:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../location/api/LocationController.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java new file mode 100644 index 0000000..734ab7d --- /dev/null +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java @@ -0,0 +1,33 @@ +package com.howWeather.howWeather_backend.domain.location.api; + +import com.howWeather.howWeather_backend.domain.location.dto.LocationWeatherRequestDto; +import com.howWeather.howWeather_backend.domain.location.dto.RegionInfoDto; +import com.howWeather.howWeather_backend.domain.location.dto.RegionTemperatureDto; +import com.howWeather.howWeather_backend.domain.location.service.LocationService; +import com.howWeather.howWeather_backend.global.Response.ApiResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/location") +@RequiredArgsConstructor +public class LocationController { + private final LocationService locationService; + @PostMapping("/region") + public ResponseEntity> getRegionFromCoords(@RequestHeader("Authorization") String accessTokenHeader, + @RequestBody @Valid LocationWeatherRequestDto request) { + RegionInfoDto region = locationService.reverseGeocode(request.getLatitude(), request.getLongitude()); + return ApiResponse.success(HttpStatus.OK, region); + } + + @PostMapping("/region/temperature") + public ResponseEntity> getRegionTemperature(@RequestHeader("Authorization") String accessTokenHeader, + @RequestBody @Valid LocationWeatherRequestDto request) { + RegionTemperatureDto dto = locationService.getRegionTemperatureFromCoords( + request.getLatitude(), request.getLongitude(), request.getTimeSlot(), request.getDate()); + return ApiResponse.success(HttpStatus.OK, dto); + } +} From 22446026f49b8295423cf1d05d1be797652b0b0a Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 06:23:49 +0900 Subject: [PATCH 07/23] =?UTF-8?q?feat:=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../location/service/LocationService.java | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/main/java/com/howWeather/howWeather_backend/domain/location/service/LocationService.java diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/service/LocationService.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/service/LocationService.java new file mode 100644 index 0000000..0839aba --- /dev/null +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/service/LocationService.java @@ -0,0 +1,86 @@ +package com.howWeather.howWeather_backend.domain.location.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.howWeather.howWeather_backend.domain.location.dto.RegionInfoDto; +import com.howWeather.howWeather_backend.domain.weather.entity.Weather; +import com.howWeather.howWeather_backend.domain.location.dto.RegionTemperatureDto; +import com.howWeather.howWeather_backend.domain.weather.repository.WeatherRepository; +import com.howWeather.howWeather_backend.global.exception.CustomException; +import com.howWeather.howWeather_backend.global.exception.ErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; + +import java.time.LocalDate; + +@Slf4j +@Service +@RequiredArgsConstructor +public class LocationService { + + @Value("${kakao.api.key}") + private String kakaoApiKey; + + private final WeatherRepository weatherRepository; + + public RegionInfoDto reverseGeocode(double latitude, double longitude) { + try { + String url = "https://dapi.kakao.com/v2/local/geo/coord2regioncode.json?x=" + longitude + "&y=" + latitude; + + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "KakaoAK " + kakaoApiKey); + HttpEntity entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, JsonNode.class); + + JsonNode documents = response.getBody().get("documents"); + if (documents.isArray() && !documents.isEmpty()) { + JsonNode region = documents.get(0); + + RegionInfoDto dto = new RegionInfoDto(); + dto.setAddress_name(region.get("address_name").asText()); + dto.setRegion_1depth_name(region.get("region_1depth_name").asText()); + dto.setRegion_2depth_name(region.get("region_2depth_name").asText()); + return dto; + } else { + throw new CustomException(ErrorCode. KAKAO_API_REGION_NOT_FOUND); + } + } catch (CustomException e) { + throw e; + } catch (Exception e) { + log.error("카카오 위치 찾기 실패: {}", e.getMessage(), e); + throw new CustomException(ErrorCode.UNKNOWN_ERROR, "카카오 위치 찾기 실패"); + } + } + + @Transactional + public RegionTemperatureDto getRegionTemperatureFromCoords(double latitude, double longitude, int timeSlot, LocalDate date) { + try { + RegionInfoDto regionInfo = reverseGeocode(latitude, longitude); + String regionName = regionInfo.getRegion_2depth_name(); + + if (date == null) { + date = LocalDate.now(); + } + + Weather weather = weatherRepository.findByRegionNameAndDateAndTimeSlot(regionName, date, timeSlot) + .orElseThrow(() -> new CustomException(ErrorCode.REGION_NOT_FOUND)); + return new RegionTemperatureDto(regionInfo, weather.getTemperature()); + } catch (CustomException e) { + throw e; + } catch (Exception e) { + log.error("카카오 위치 찾기 실패: {}", e.getMessage(), e); + throw new CustomException(ErrorCode.UNKNOWN_ERROR, "카카오 위치 찾기 실패"); + } + } +} + From bd9624990fe36a86c1362e1545fee465babd5df4 Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 07:41:44 +0900 Subject: [PATCH 08/23] =?UTF-8?q?feat:=20=EB=A9=A4=EB=B2=84=EC=97=90=20?= =?UTF-8?q?=EC=A7=80=EC=97=AD=20=ED=95=84=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 --- .../howWeather_backend/domain/member/entity/Member.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/member/entity/Member.java b/src/main/java/com/howWeather/howWeather_backend/domain/member/entity/Member.java index 694bedc..8f6b49f 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/member/entity/Member.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/member/entity/Member.java @@ -50,6 +50,9 @@ public class Member implements UserDetails { @Column(name="body_type") private int bodyType; + @Column(name="region_name") + private String regionName; + @Column(name="gender", nullable = false) private int gender; @@ -116,6 +119,10 @@ public void changeNickname(String s) { this.nickname = s; } + public void changeRegion(String regionName) { + this.regionName = regionName; + } + public void changeConstitution(int v) { this.constitution = v; } From 057b210c2ec2d8cbeaf7ab92433d8cecba54ce25 Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 07:42:12 +0900 Subject: [PATCH 09/23] =?UTF-8?q?feat:=20=EC=A7=80=EC=97=AD=20dto=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/dto/RegionDto.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/com/howWeather/howWeather_backend/domain/member/dto/RegionDto.java diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/member/dto/RegionDto.java b/src/main/java/com/howWeather/howWeather_backend/domain/member/dto/RegionDto.java new file mode 100644 index 0000000..0bff4b6 --- /dev/null +++ b/src/main/java/com/howWeather/howWeather_backend/domain/member/dto/RegionDto.java @@ -0,0 +1,11 @@ +package com.howWeather.howWeather_backend.domain.member.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class RegionDto { + @NotNull @NotBlank + private String regionName; +} From 68c68980db9ad4c5da8844b8d4754d13a80285da Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 07:42:23 +0900 Subject: [PATCH 10/23] =?UTF-8?q?feat:=20=EC=A7=80=EC=97=AD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/api/MyAccountController.java | 10 ++++++++ .../domain/member/service/AuthService.java | 25 ++++++++++++++++++- .../weather/repository/RegionRepository.java | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/member/api/MyAccountController.java b/src/main/java/com/howWeather/howWeather_backend/domain/member/api/MyAccountController.java index 12ae721..8563418 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/member/api/MyAccountController.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/member/api/MyAccountController.java @@ -3,6 +3,7 @@ import com.howWeather.howWeather_backend.domain.member.dto.NicknameDto; import com.howWeather.howWeather_backend.domain.member.dto.ProfileChangeIntDto; import com.howWeather.howWeather_backend.domain.member.dto.ProfileDto; +import com.howWeather.howWeather_backend.domain.member.dto.RegionDto; import com.howWeather.howWeather_backend.domain.member.entity.Member; import com.howWeather.howWeather_backend.domain.member.repository.MemberRepository; import com.howWeather.howWeather_backend.domain.member.service.AuthService; @@ -67,4 +68,13 @@ public ResponseEntity> updateConstitution(@RequestHeader("Au authService.updateConstitution(member, profileChangeDto); return ApiResponse.success(HttpStatus.OK, "체질을 성공적으로 수정하였습니다."); } + + @PatchMapping("/update-location") + @CheckAuthenticatedUser + public ResponseEntity> updateLocation(@RequestHeader("Authorization") String accessTokenHeader, + @Valid @RequestBody RegionDto regionDto, + @AuthenticationPrincipal Member member) { + authService.updateLocation(member, regionDto); + return ApiResponse.success(HttpStatus.OK, "지역을 성공적으로 수정하였습니다."); + } } diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java b/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java index 3291b08..e8f0fa7 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java @@ -7,6 +7,7 @@ import com.howWeather.howWeather_backend.domain.member.entity.Member; import com.howWeather.howWeather_backend.domain.member.repository.MemberRepository; import com.howWeather.howWeather_backend.domain.closet.repository.ClosetRepository; +import com.howWeather.howWeather_backend.domain.weather.repository.RegionRepository; import com.howWeather.howWeather_backend.global.exception.CustomException; import com.howWeather.howWeather_backend.global.exception.ErrorCode; import com.howWeather.howWeather_backend.global.exception.LoginException; @@ -48,6 +49,7 @@ public class AuthService { private final FcmAlarmPreferenceService fcmAlarmPreferenceService; private final CustomOAuth2UserService customOAuth2UserService; private final OAuth2AuthorizedClientService authorizedClientService; + private final RegionRepository regionRepository; @Transactional public void signup(SignupRequestDto signupRequestDto) { @@ -299,6 +301,27 @@ public void updateConstitution(Member member, ProfileChangeIntDto profileChangeD } } + @Transactional + public void updateLocation(Member member, RegionDto regionDto) { + try { + validateRegion(regionDto.getRegionName()); + Member persistedMember = memberRepository.findById(member.getId()) + .orElseThrow(() -> new CustomException(ErrorCode.ID_NOT_FOUND, "회원 정보를 찾을 수 없습니다.")); + persistedMember.changeRegion(regionDto.getRegionName()); + } catch (CustomException e) { + throw e; + } catch (Exception e) { + log.error("지역 수정 중 에러 발생: {}", e.getMessage(), e); + throw new CustomException(ErrorCode.UNKNOWN_ERROR, "지역 수정 중 오류가 발생했습니다."); + } + } + + private void validateRegion(String regionName) { + if (regionName == null || regionName.isEmpty() || !regionRepository.existsByName(regionName)) { + throw new CustomException(ErrorCode.REGION_NOT_FOUND, "해당 지역은 서비스를 제공하지 않습니다."); + } + } + private void validateIntData(Integer data, int s, int e) { if (data < s || data > e) { throw new CustomException(ErrorCode.INVALID_INPUT, "유효하지 않은 입력값입니다."); @@ -366,7 +389,7 @@ public String resetPassword(String identifier) { memberRepository.flush(); mailService.sendTemporaryPassword(member.getEmail(), tempPassword); return member.getEmail(); - } catch (CustomException e) { + } catch (CustomException e) { throw e; } catch (Exception e) { log.error("비밀번호 초기화 중 에러 발생: {}", e.getMessage(), e); diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java b/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java index b0f36af..72917b2 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java @@ -6,4 +6,5 @@ @Registered public interface RegionRepository extends JpaRepository { + boolean existsByName(String name); } From 15edceec131a26a14a7526945f426c658917097c Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 07:42:54 +0900 Subject: [PATCH 11/23] =?UTF-8?q?chore:=20=EB=88=84=EB=9D=BD=ED=95=9C=20?= =?UTF-8?q?=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/location/api/LocationController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java index 734ab7d..7d10753 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java @@ -5,6 +5,7 @@ import com.howWeather.howWeather_backend.domain.location.dto.RegionTemperatureDto; import com.howWeather.howWeather_backend.domain.location.service.LocationService; import com.howWeather.howWeather_backend.global.Response.ApiResponse; +import com.howWeather.howWeather_backend.global.jwt.CheckAuthenticatedUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -17,6 +18,7 @@ public class LocationController { private final LocationService locationService; @PostMapping("/region") + @CheckAuthenticatedUser public ResponseEntity> getRegionFromCoords(@RequestHeader("Authorization") String accessTokenHeader, @RequestBody @Valid LocationWeatherRequestDto request) { RegionInfoDto region = locationService.reverseGeocode(request.getLatitude(), request.getLongitude()); @@ -24,6 +26,7 @@ public ResponseEntity> getRegionFromCoords(@RequestHe } @PostMapping("/region/temperature") + @CheckAuthenticatedUser public ResponseEntity> getRegionTemperature(@RequestHeader("Authorization") String accessTokenHeader, @RequestBody @Valid LocationWeatherRequestDto request) { RegionTemperatureDto dto = locationService.getRegionTemperatureFromCoords( From c7c04574f865dc8b93646e32675eb87d61ba815e Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 14:40:47 +0900 Subject: [PATCH 12/23] =?UTF-8?q?fix:=20post=20->=20get=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/location/api/LocationController.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java index 7d10753..70fb8ff 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/api/LocationController.java @@ -1,31 +1,32 @@ package com.howWeather.howWeather_backend.domain.location.api; import com.howWeather.howWeather_backend.domain.location.dto.LocationWeatherRequestDto; -import com.howWeather.howWeather_backend.domain.location.dto.RegionInfoDto; import com.howWeather.howWeather_backend.domain.location.dto.RegionTemperatureDto; import com.howWeather.howWeather_backend.domain.location.service.LocationService; import com.howWeather.howWeather_backend.global.Response.ApiResponse; import com.howWeather.howWeather_backend.global.jwt.CheckAuthenticatedUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +@Slf4j @RestController @RequestMapping("/api/location") @RequiredArgsConstructor public class LocationController { private final LocationService locationService; - @PostMapping("/region") + @GetMapping("/region") @CheckAuthenticatedUser - public ResponseEntity> getRegionFromCoords(@RequestHeader("Authorization") String accessTokenHeader, + public ResponseEntity> getRegionFromCoords(@RequestHeader("Authorization") String accessTokenHeader, @RequestBody @Valid LocationWeatherRequestDto request) { - RegionInfoDto region = locationService.reverseGeocode(request.getLatitude(), request.getLongitude()); + String region = locationService.reverseGeocode(request.getLatitude(), request.getLongitude()); return ApiResponse.success(HttpStatus.OK, region); } - @PostMapping("/region/temperature") + @GetMapping("/region/temperature") @CheckAuthenticatedUser public ResponseEntity> getRegionTemperature(@RequestHeader("Authorization") String accessTokenHeader, @RequestBody @Valid LocationWeatherRequestDto request) { From a7c36c98d14784168f55aa887799c65d7e87e7fd Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 14:41:07 +0900 Subject: [PATCH 13/23] =?UTF-8?q?refactor:=20=EB=B0=98=ED=99=98=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EA=B0=84=EC=86=8C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/location/dto/RegionTemperatureDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionTemperatureDto.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionTemperatureDto.java index 1572c50..299abdd 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionTemperatureDto.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/dto/RegionTemperatureDto.java @@ -6,6 +6,6 @@ @Data @AllArgsConstructor public class RegionTemperatureDto { - private RegionInfoDto regionInfo; + private String regionName; private Double temperature; } From 105350748cfc4f2e4e0525576942badcb0d76837 Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Mon, 30 Jun 2025 14:42:01 +0900 Subject: [PATCH 14/23] =?UTF-8?q?feat:=20=EC=99=84=EC=A0=84=ED=9E=88=20?= =?UTF-8?q?=EB=98=91=EA=B0=99=EC=9D=80=20=EC=A7=80=EC=97=AD=EB=AA=85?= =?UTF-8?q?=EC=9D=84=20=EC=B0=BE=EC=A7=80=20=EB=AA=BB=ED=95=A0=20=EB=95=8C?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 최장 공통 prefix 지역명을 반환하도록 수정 --- .../location/service/LocationService.java | 84 +++++++++++++++---- .../weather/repository/RegionRepository.java | 9 ++ 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/location/service/LocationService.java b/src/main/java/com/howWeather/howWeather_backend/domain/location/service/LocationService.java index 0839aba..1d59f60 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/location/service/LocationService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/location/service/LocationService.java @@ -2,8 +2,10 @@ import com.fasterxml.jackson.databind.JsonNode; import com.howWeather.howWeather_backend.domain.location.dto.RegionInfoDto; +import com.howWeather.howWeather_backend.domain.weather.entity.Region; import com.howWeather.howWeather_backend.domain.weather.entity.Weather; import com.howWeather.howWeather_backend.domain.location.dto.RegionTemperatureDto; +import com.howWeather.howWeather_backend.domain.weather.repository.RegionRepository; import com.howWeather.howWeather_backend.domain.weather.repository.WeatherRepository; import com.howWeather.howWeather_backend.global.exception.CustomException; import com.howWeather.howWeather_backend.global.exception.ErrorCode; @@ -19,6 +21,8 @@ import org.springframework.http.HttpMethod; import java.time.LocalDate; +import java.util.List; +import java.util.Optional; @Slf4j @Service @@ -29,9 +33,16 @@ public class LocationService { private String kakaoApiKey; private final WeatherRepository weatherRepository; + private final RegionRepository regionRepository; - public RegionInfoDto reverseGeocode(double latitude, double longitude) { + public String reverseGeocode(double latitude, double longitude) { try { + boolean latitudeValidation = validateData("latitude", latitude, -90, 90); + boolean longitudeValidation = validateData("longitude", longitude, -180, 180); + + if (!latitudeValidation || !longitudeValidation) { + throw new CustomException(ErrorCode.INVALID_INPUT, "위/경도 값이 올바르지 않습니다."); + } String url = "https://dapi.kakao.com/v2/local/geo/coord2regioncode.json?x=" + longitude + "&y=" + latitude; RestTemplate restTemplate = new RestTemplate(); @@ -46,13 +57,10 @@ public RegionInfoDto reverseGeocode(double latitude, double longitude) { if (documents.isArray() && !documents.isEmpty()) { JsonNode region = documents.get(0); - RegionInfoDto dto = new RegionInfoDto(); - dto.setAddress_name(region.get("address_name").asText()); - dto.setRegion_1depth_name(region.get("region_1depth_name").asText()); - dto.setRegion_2depth_name(region.get("region_2depth_name").asText()); - return dto; + String regionName = region.get("region_1depth_name").asText() + " " + region.get("region_2depth_name").asText(); + return regionName; } else { - throw new CustomException(ErrorCode. KAKAO_API_REGION_NOT_FOUND); + throw new CustomException(ErrorCode.KAKAO_API_REGION_NOT_FOUND); } } catch (CustomException e) { throw e; @@ -62,25 +70,71 @@ public RegionInfoDto reverseGeocode(double latitude, double longitude) { } } + private boolean validateData(String name, double data, int low, int high) { + if (data < low || data > high) { + return false; + } + return true; + } + @Transactional - public RegionTemperatureDto getRegionTemperatureFromCoords(double latitude, double longitude, int timeSlot, LocalDate date) { + public RegionTemperatureDto getRegionTemperatureFromCoords( + double latitude, double longitude, int timeSlot, LocalDate date) { try { - RegionInfoDto regionInfo = reverseGeocode(latitude, longitude); - String regionName = regionInfo.getRegion_2depth_name(); + String fullRegion = reverseGeocode(latitude, longitude); + String matchedRegionName = resolveRegionName(fullRegion); if (date == null) { date = LocalDate.now(); } - Weather weather = weatherRepository.findByRegionNameAndDateAndTimeSlot(regionName, date, timeSlot) - .orElseThrow(() -> new CustomException(ErrorCode.REGION_NOT_FOUND)); - return new RegionTemperatureDto(regionInfo, weather.getTemperature()); + Weather weather = weatherRepository.findByRegionNameAndDateAndTimeSlot( + matchedRegionName, date, timeSlot + ).orElseThrow(() -> new CustomException(ErrorCode.REGION_NOT_FOUND)); + + return new RegionTemperatureDto(matchedRegionName, weather.getTemperature()); + } catch (CustomException e) { throw e; } catch (Exception e) { - log.error("카카오 위치 찾기 실패: {}", e.getMessage(), e); - throw new CustomException(ErrorCode.UNKNOWN_ERROR, "카카오 위치 찾기 실패"); + log.error("위치 처리 실패: {}", e.getMessage(), e); + throw new CustomException(ErrorCode.UNKNOWN_ERROR, "위치 처리 실패"); + } + } + + public String resolveRegionName(String fullRegionFromKakao) { + Optional exactMatch = regionRepository.findByName(fullRegionFromKakao); + if (exactMatch.isPresent()) { + return exactMatch.get().getName(); + } + + List regionNames = regionRepository.findAllRegionNames(); + + String bestMatch = null; + int maxPrefixLength = 0; + + for (String regionName : regionNames) { + int prefixLength = getCommonPrefixLength(fullRegionFromKakao, regionName); + if (prefixLength > maxPrefixLength) { + maxPrefixLength = prefixLength; + bestMatch = regionName; + } + } + + if (bestMatch == null) { + throw new CustomException(ErrorCode.REGION_NOT_FOUND); + } + + return bestMatch; + } + + private int getCommonPrefixLength(String a, String b) { + int minLength = Math.min(a.length(), b.length()); + int i = 0; + while (i < minLength && a.charAt(i) == b.charAt(i)) { + i++; } + return i; } } diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java b/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java index 72917b2..ed382ef 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java @@ -3,8 +3,17 @@ import com.howWeather.howWeather_backend.domain.weather.entity.Region; import jdk.jfr.Registered; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; +import java.util.Optional; @Registered public interface RegionRepository extends JpaRepository { boolean existsByName(String name); + + Optional findByName(String name); + + @Query("SELECT r.name FROM Region r") + List findAllRegionNames(); } From a950fb7ed6090690197c9360f63bb653c8c70ec9 Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Fri, 4 Jul 2025 16:22:25 +0900 Subject: [PATCH 15/23] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/api/MyAccountController.java | 8 ++++++++ .../domain/member/service/AuthService.java | 20 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/member/api/MyAccountController.java b/src/main/java/com/howWeather/howWeather_backend/domain/member/api/MyAccountController.java index 8563418..dded81b 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/member/api/MyAccountController.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/member/api/MyAccountController.java @@ -69,6 +69,14 @@ public ResponseEntity> updateConstitution(@RequestHeader("Au return ApiResponse.success(HttpStatus.OK, "체질을 성공적으로 수정하였습니다."); } + @GetMapping("/location") + @CheckAuthenticatedUser + public ResponseEntity> getLocation(@RequestHeader("Authorization") String accessTokenHeader, + @AuthenticationPrincipal Member member) { + String location = authService.getLoccation(member); + return ApiResponse.success(HttpStatus.OK, location); + } + @PatchMapping("/update-location") @CheckAuthenticatedUser public ResponseEntity> updateLocation(@RequestHeader("Authorization") String accessTokenHeader, diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java b/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java index e8f0fa7..050221f 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java @@ -301,6 +301,24 @@ public void updateConstitution(Member member, ProfileChangeIntDto profileChangeD } } + @Transactional(readOnly = true) + public String getLoccation(Member member) { + try { + String region = member.getRegionName(); + + if (region == null || region.trim().isEmpty()) + return "서울특별시 용산구"; + + return region; + + } catch (CustomException e) { + throw e; + }catch (Exception e) { + log.error("지역 조회 중 에러 발생: {}", e.getMessage(), e); + throw new CustomException(ErrorCode.UNKNOWN_ERROR, "지역 조회 중 오류가 발생했습니다."); + } + } + @Transactional public void updateLocation(Member member, RegionDto regionDto) { try { @@ -318,7 +336,7 @@ public void updateLocation(Member member, RegionDto regionDto) { private void validateRegion(String regionName) { if (regionName == null || regionName.isEmpty() || !regionRepository.existsByName(regionName)) { - throw new CustomException(ErrorCode.REGION_NOT_FOUND, "해당 지역은 서비스를 제공하지 않습니다."); + throw new CustomException(ErrorCode.REGION_NOT_FOUND); } } From 1b6de1053b275d5ec6151f366041d77e998704cc Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Fri, 4 Jul 2025 16:22:50 +0900 Subject: [PATCH 16/23] =?UTF-8?q?chore:=20=EC=BB=A4=EC=8A=A4=ED=85=80=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/weather/service/WeatherService.java | 2 +- .../howWeather_backend/global/exception/ErrorCode.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java b/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java index d4e4ea6..e52b8e0 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java @@ -53,7 +53,7 @@ public double getTemperature(WeatherSimpleDto dto) { LocalDate localDate = LocalDate.parse(dto.getDate()); return weatherRepository.findByRegionNameAndDateAndTimeSlot(dto.getCity(), localDate, dto.getTimeSlot()) .map(Weather::getTemperature) - .orElseThrow(() -> new CustomException(ErrorCode.REGION_NOT_FOUND, "해당 지역과 시간대의 온도는 현재 제공하지 않습니다.")); + .orElseThrow(() -> new CustomException(ErrorCode. REGION_AND_TIME_NOT_FOUND)); } catch (CustomException e){ throw e; } catch (Exception e) { diff --git a/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java b/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java index bef7a2f..0aabd64 100644 --- a/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java +++ b/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java @@ -58,7 +58,8 @@ public enum ErrorCode { // 닐씨 WEATHER_API_CALL_ERROR("날씨 API를 호출하는 중 오류가 발생했습니다", HttpStatus.SERVICE_UNAVAILABLE), NO_BODY_ERROR("날씨 데이터가 존재하지 않습니다", HttpStatus.NOT_FOUND), - REGION_NOT_FOUND("해당 지역과 시간대에 대해서는 서비스를 제공하지 않습니다.", HttpStatus.NOT_FOUND), + REGION_AND_TIME_NOT_FOUND("해당 지역과 시간대의 온도는 현재 제공하지 않습니다.", HttpStatus.NOT_FOUND), + REGION_NOT_FOUND("해당 지역에 대해서는 서비스를 제공하지 않습니다.", HttpStatus.NOT_FOUND), // 알림 관리 DUPLICATE_FCM_TOKEN("이미 등록된 FCM 토큰입니다.", HttpStatus.CONFLICT), From deb4a6377b9e553a8583748e89077cf7423573ef Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Fri, 4 Jul 2025 16:37:57 +0900 Subject: [PATCH 17/23] =?UTF-8?q?feat:=20=EC=98=88=EB=B3=B4=EA=B0=80=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EC=A7=80=EC=97=AD=EB=A7=8C=20?= =?UTF-8?q?=EC=98=88=EB=B3=B4=20api=20=ED=98=B8=EC=B6=9C=ED=95=98=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 - region 엔티티에 해당 데이터 예보 데이터를 받을 사람의 수를 저장하는 필드 추가 --- .../domain/weather/entity/Region.java | 12 +++++ .../weather/repository/RegionRepository.java | 2 + .../weather/service/WeatherService.java | 50 ++++++++++--------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/weather/entity/Region.java b/src/main/java/com/howWeather/howWeather_backend/domain/weather/entity/Region.java index a62eeb2..46cd3b3 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/weather/entity/Region.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/weather/entity/Region.java @@ -21,4 +21,16 @@ public class Region { private double lon; private String name; + + private int currentUserCount; + + public void incrementCurrentUserCount() { + this.currentUserCount++; + } + + public void decrementCurrentUserCount() { + if (this.currentUserCount > 0) { + this.currentUserCount--; + } + } } diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java b/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java index ed382ef..0070d0d 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/weather/repository/RegionRepository.java @@ -16,4 +16,6 @@ public interface RegionRepository extends JpaRepository { @Query("SELECT r.name FROM Region r") List findAllRegionNames(); + + List findByCurrentUserCountGreaterThan(int count); } diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java b/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java index e52b8e0..144ed8b 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java @@ -61,37 +61,41 @@ public double getTemperature(WeatherSimpleDto dto) { } } + @Transactional public void fetchHourlyForecast() { - double lat = 37.53138497; - double lon = 126.979907; - String regionName = "서울특별시 용산구"; // TODO: 추후 동적 지역명으로 변경 + List regions = regionRepository.findByCurrentUserCountGreaterThan(0); + LocalDate baseDate = LocalDate.now(); - List forecasts = weatherApiClient.fetchForecast(lat, lon); + List allForecasts = new ArrayList<>(); - LocalDate baseDate = LocalDate.now(); - int cnt = 0; + for (Region region : regions) { + double lat = region.getLat(); + double lon = region.getLon(); + String regionName = region.getName(); - List entities = new ArrayList<>(); + List forecasts = weatherApiClient.fetchForecast(lat, lon); - for (WeatherPredictDto dto : forecasts) { - LocalDate forecastDate = baseDate.plusDays(cnt / 5); + int cnt = 0; + for (WeatherPredictDto dto : forecasts) { + LocalDate forecastDate = baseDate.plusDays(cnt / 5); - WeatherForecast entity = WeatherForecast.builder() - .regionName(regionName) - .forecastDate(forecastDate) - .hour(dto.getHour()) - .temperature(dto.getTemperature()) - .humidity(dto.getHumidity()) - .windSpeed(dto.getWindSpeed()) - .precipitation(dto.getPrecipitation()) - .cloudAmount(dto.getCloudAmount()) - .feelsLike(dto.getFeelsLike()) - .build(); + WeatherForecast entity = WeatherForecast.builder() + .regionName(regionName) + .forecastDate(forecastDate) + .hour(dto.getHour()) + .temperature(dto.getTemperature()) + .humidity(dto.getHumidity()) + .windSpeed(dto.getWindSpeed()) + .precipitation(dto.getPrecipitation()) + .cloudAmount(dto.getCloudAmount()) + .feelsLike(dto.getFeelsLike()) + .build(); - entities.add(entity); - cnt++; + allForecasts.add(entity); + cnt++; + } } - weatherForecastRepository.saveAll(entities); + weatherForecastRepository.saveAll(allForecasts); } } From 53b8fe3b13b5a6cfe67b6f2dd159e60b08b0cc1e Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Fri, 4 Jul 2025 16:38:14 +0900 Subject: [PATCH 18/23] =?UTF-8?q?chore:=20=EC=A3=BC=EC=84=9D=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?log=EB=AC=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai_model/api/AiInternalController.java | 60 +++++++++---------- .../ai_model/schedular/ModelSchedular.java | 9 +-- .../service/RecommendationService.java | 6 +- 3 files changed, 31 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/api/AiInternalController.java b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/api/AiInternalController.java index ca9a03e..8d481f1 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/api/AiInternalController.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/api/AiInternalController.java @@ -62,7 +62,6 @@ public ResponseEntity> sendAllUsersPredictionDataSecure() { public ResponseEntity> saveRecommendations(@RequestBody Map encryptedBody) { try { String decryptedJson = aesCipher.decrypt(encryptedBody); - log.info("🔓 복호화된 데이터:\n{}", decryptedJson); if (decryptedJson.startsWith("\"") && decryptedJson.endsWith("\"")) { decryptedJson = objectMapper.readValue(decryptedJson, String.class); @@ -118,34 +117,33 @@ private Map encryptPredictionData(List d } // TODO : 암/복호화 API 연결 이후 아래 코드 삭제 - - @PostMapping("/prediction") - public ResponseEntity> sendAllUsersPredictionData() { - try { - List members = memberRepository.findAllByIsDeletedFalse(); - - List allDtos = members.stream() - .filter(member -> member.getCloset() != null) - .map(aiInternalService::makePredictRequest) - .collect(Collectors.toList()); - - return ResponseEntity.ok(allDtos); - } catch (Exception e) { - log.error(e.getMessage()); - } - return null; - } - - @PostMapping("/recommendation") - public ResponseEntity> saveRecommendations(@RequestBody ModelClothingRecommendationDto dto) { - recommendationService.save(dto); - return ApiResponse.success(HttpStatus.OK, "예측 결과를 성공적으로 저장했습니다."); - } - - @PostMapping("/history") - public ResponseEntity>> getSimilarHistory(@RequestBody HistoryRequestDto dto) { - dto.validate(); - List result = recordCalendarService.getMemberHistory(dto); - return ApiResponse.success(HttpStatus.OK, result); - } +// @PostMapping("/prediction") +// public ResponseEntity> sendAllUsersPredictionData() { +// try { +// List members = memberRepository.findAllByIsDeletedFalse(); +// +// List allDtos = members.stream() +// .filter(member -> member.getCloset() != null) +// .map(aiInternalService::makePredictRequest) +// .collect(Collectors.toList()); +// +// return ResponseEntity.ok(allDtos); +// } catch (Exception e) { +// log.error(e.getMessage()); +// } +// return null; +// } +// +// @PostMapping("/recommendation") +// public ResponseEntity> saveRecommendations(@RequestBody ModelClothingRecommendationDto dto) { +// recommendationService.save(dto); +// return ApiResponse.success(HttpStatus.OK, "예측 결과를 성공적으로 저장했습니다."); +// } +// +// @PostMapping("/history") +// public ResponseEntity>> getSimilarHistory(@RequestBody HistoryRequestDto dto) { +// dto.validate(); +// List result = recordCalendarService.getMemberHistory(dto); +// return ApiResponse.success(HttpStatus.OK, result); +// } } diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/schedular/ModelSchedular.java b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/schedular/ModelSchedular.java index 30c93d6..4bb8d23 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/schedular/ModelSchedular.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/schedular/ModelSchedular.java @@ -112,15 +112,8 @@ public void pushPredictionAESDataToAiServer() { return; } - ObjectMapper objectMapper = new ObjectMapper(); - String plainJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(allDtos); - log.info("✅ 예측 데이터 (평문 JSON):\n{}", plainJson); - Map encrypted = encryptPredictionData(allDtos); - String encryptedJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(encrypted); - log.info("🔒 예측 데이터 (암호화된 JSON):\n{}", encryptedJson); - HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity> requestEntity = new HttpEntity<>(encrypted, headers); @@ -139,7 +132,7 @@ public void pushPredictionAESDataToAiServer() { private Map encryptPredictionData(List dtos) { try { String jsonData = objectMapper.writeValueAsString(dtos); - return aesCipher.encrypt(jsonData); // { "iv": "...", "payload": "..." } + return aesCipher.encrypt(jsonData); } catch (Exception e) { log.error("예측 데이터 암호화 실패: {}", e.getMessage()); return null; diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java index 574c5e4..dced032 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java @@ -85,7 +85,6 @@ private List makeOuterList(Closet closet, List outers) { } } - // if (result.isEmpty()) throw new CustomException(ErrorCode.NO_RECOMMEND_OUTER); return new ArrayList<>(resultSet); } @@ -103,7 +102,6 @@ private List makeUpperList(Closet closet, List tops) { } } } - // if (result.isEmpty()) throw new CustomException(ErrorCode.NO_RECOMMEND_UPPER); return new ArrayList<>(resultSet); } @@ -123,12 +121,11 @@ private List makeWeatherFeeling(Map predicti List forecasts = weatherForecastRepository .findByRegionNameAndForecastDateAndHourIn(regionName, forecastDate, hours); - // hour 기준으로 forecast 객체를 통째로 저장 (중복 방지 및 날짜 포함용) Map hourToForecastMap = forecasts.stream() .collect(Collectors.toMap( WeatherForecast::getHour, forecast -> forecast, - (oldVal, newVal) -> newVal // 중복 시간대가 있다면 최신 데이터로 덮기 + (oldVal, newVal) -> newVal )); for (Map.Entry entry : predictionMap.entrySet()) { @@ -160,7 +157,6 @@ private List getModelPrediction(Long id, LocalDate now) throw new CustomException(ErrorCode.NO_PREDICT_DATA); } return list; - // return clothingRecommendationRepository.findByMemberIdAndDate(id, now); } @Transactional From 678a772225d61d73b3ac329931e4014f9f4bb32c Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Fri, 4 Jul 2025 16:43:42 +0900 Subject: [PATCH 19/23] =?UTF-8?q?feat:=20=EC=A7=80=EC=97=AD=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EC=A7=80=EC=97=AD=20=EC=B9=B4=EC=9A=B4?= =?UTF-8?q?=ED=8A=B8=20=EB=B3=80=EA=B2=BD=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/service/AuthService.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java b/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java index 050221f..9855b79 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java @@ -7,6 +7,7 @@ import com.howWeather.howWeather_backend.domain.member.entity.Member; import com.howWeather.howWeather_backend.domain.member.repository.MemberRepository; import com.howWeather.howWeather_backend.domain.closet.repository.ClosetRepository; +import com.howWeather.howWeather_backend.domain.weather.entity.Region; import com.howWeather.howWeather_backend.domain.weather.repository.RegionRepository; import com.howWeather.howWeather_backend.global.exception.CustomException; import com.howWeather.howWeather_backend.global.exception.ErrorCode; @@ -323,9 +324,23 @@ public String getLoccation(Member member) { public void updateLocation(Member member, RegionDto regionDto) { try { validateRegion(regionDto.getRegionName()); + Member persistedMember = memberRepository.findById(member.getId()) .orElseThrow(() -> new CustomException(ErrorCode.ID_NOT_FOUND, "회원 정보를 찾을 수 없습니다.")); - persistedMember.changeRegion(regionDto.getRegionName()); + + String oldRegionName = persistedMember.getRegionName(); + String newRegionName = regionDto.getRegionName(); + + if (oldRegionName == null || !newRegionName.equals(oldRegionName)) { + if (oldRegionName != null) { + regionRepository.findByName(oldRegionName).ifPresent(Region::decrementCurrentUserCount); + } + + Region newRegion = regionRepository.findByName(newRegionName) + .orElseThrow(() -> new CustomException(ErrorCode.REGION_NOT_FOUND)); + newRegion.incrementCurrentUserCount(); + persistedMember.changeRegion(newRegionName); + } } catch (CustomException e) { throw e; } catch (Exception e) { From 1795980f68f2b4bf359078f7a462f8bfad8c1c94 Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Fri, 4 Jul 2025 16:48:41 +0900 Subject: [PATCH 20/23] =?UTF-8?q?feat:=20=EA=B8=B0=EB=B3=B8=20=EC=A7=80?= =?UTF-8?q?=EC=97=AD=EC=9D=B8=20=EC=9A=A9=EC=82=B0=EA=B5=AC=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B4=EC=A1=B0=EA=B1=B4=20=EC=98=88=EB=B3=B4=EB=A5=BC=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8F=84=EB=A1=9D=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 --- .../domain/ai_model/service/RecommendationService.java | 2 +- .../domain/weather/service/WeatherService.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java index dced032..3246348 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java @@ -135,7 +135,7 @@ private List makeWeatherFeeling(Map predicti if (forecast != null) { WeatherFeelingDto dto = WeatherFeelingDto.builder() - .date(forecast.getForecastDate()) // 추가 + .date(forecast.getForecastDate()) .time(hour) .feeling(feeling) .temperature(forecast.getTemperature()) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java b/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java index 144ed8b..12063ae 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/weather/service/WeatherService.java @@ -66,6 +66,13 @@ public void fetchHourlyForecast() { List regions = regionRepository.findByCurrentUserCountGreaterThan(0); LocalDate baseDate = LocalDate.now(); + String defaultRegionName = "서울특별시 용산구"; + boolean hasDefaultRegion = regions.stream() + .anyMatch(r -> r.getName().equals(defaultRegionName)); + if (!hasDefaultRegion) { + regionRepository.findByName(defaultRegionName).ifPresent(regions::add); + } + List allForecasts = new ArrayList<>(); for (Region region : regions) { @@ -97,5 +104,4 @@ public void fetchHourlyForecast() { } weatherForecastRepository.saveAll(allForecasts); } - } From 082f312897e3ec08d63984f54e60855b138f993f Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Fri, 4 Jul 2025 16:55:56 +0900 Subject: [PATCH 21/23] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=84=A0=ED=83=9D=ED=95=9C=20=EC=A7=80=EC=97=AD=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20ai=20=EB=AA=A8=EB=8D=B8?= =?UTF-8?q?=EC=97=90=20=EB=B3=B4=EB=82=B4=EB=8F=84=EB=A1=9D=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 --- .../domain/ai_model/service/AiInternalService.java | 7 ++++--- .../domain/weather/scheduler/WeatherScheduler.java | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/AiInternalService.java b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/AiInternalService.java index d7a16a9..cb1f9e7 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/AiInternalService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/AiInternalService.java @@ -28,7 +28,7 @@ public class AiInternalService { @Transactional public AiPredictionRequestDto makePredictRequest(Member member) { - List dtoList = getWeatherForecast(); + List dtoList = getWeatherForecast(member); return AiPredictionRequestDto.builder() .userId(member.getId()) .bodyTypeLabel(member.getConstitution()) @@ -37,11 +37,12 @@ public AiPredictionRequestDto makePredictRequest(Member member) { .build(); } - private List getWeatherForecast() { + private List getWeatherForecast(Member member) { List targetHours = List.of(9, 12, 15, 18, 21); + String regionName = member.getRegionName() != null ? member.getRegionName() : "서울특별시 용산구"; List forecasts = weatherForecastRepository - .findByRegionNameAndForecastDateAndHourIn("서울특별시 용산구", LocalDate.now(), targetHours); + .findByRegionNameAndForecastDateAndHourIn(regionName, LocalDate.now(), targetHours); return forecasts.stream() .map(this::convertToDto) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/weather/scheduler/WeatherScheduler.java b/src/main/java/com/howWeather/howWeather_backend/domain/weather/scheduler/WeatherScheduler.java index dc1f99d..1b27c67 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/weather/scheduler/WeatherScheduler.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/weather/scheduler/WeatherScheduler.java @@ -57,16 +57,16 @@ public void deleteOldWeatherData() { /** * 매일 오전 4시 30분에 ai 학습에 이용될 오늘의 예보 데이터를 가져옵니다. */ - @Scheduled(cron = "0 30 4 * * *", zone = "Asia/Seoul") // 매일 오전 4시 30분에 예보 데이터 + @Scheduled(cron = "0 30 4 * * *", zone = "Asia/Seoul") public void fetchDailyOneCallWeather() { - log.info("Forecast API로 용산구의 날씨 예보를 가져옵니다. 시간: {}", LocalDateTime.now()); + log.info("Forecast API로 용산구를 포함한 사용자가 예보를 희망하는 지역의 날씨 예보를 가져옵니다. 시간: {}", LocalDateTime.now()); weatherService.fetchHourlyForecast(); } /** * 매일 오전 7시에 오늘 이전의 예보 데이터를 삭제합니다. */ - @Scheduled(cron = "0 0 7 * * *", zone = "Asia/Seoul") // 매일 오전 7시 + @Scheduled(cron = "0 0 7 * * *", zone = "Asia/Seoul") public void deleteOldForecastData() { LocalDate today = LocalDate.now(); weatherForecastRepository.deleteByForecastDateBefore(today); From 519710eaa969ca9bed7de56bb56e35245587259e Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Fri, 4 Jul 2025 17:00:27 +0900 Subject: [PATCH 22/23] =?UTF-8?q?feat:=20=ED=8F=AC=EB=A5=B8=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=84=EC=86=A1=20?= =?UTF-8?q?=EC=8B=9C=EC=97=90=EB=8F=84=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=84=A0=ED=83=9D=ED=95=9C=20=EC=A7=80=EC=97=AD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=B4=EB=82=B4=EB=8F=84=EB=A1=9D=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 --- .../ai_model/service/RecommendationService.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java index 3246348..87fbc85 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java @@ -43,16 +43,16 @@ public List getRecommendList(Member member) { Closet closet = getClosetWithAll(member); List result = new ArrayList<>(); for (ClothingRecommendation recommendation : modelPredictList) { - result.add(makeResultForPredict(closet, recommendation)); + result.add(makeResultForPredict(closet, recommendation, member)); } return result; } - private RecommendPredictDto makeResultForPredict(Closet closet, ClothingRecommendation recommendation) { + private RecommendPredictDto makeResultForPredict(Closet closet, ClothingRecommendation recommendation, Member member) { try { List upperTypeList = makeUpperList(closet, recommendation.getTops()); List outerTypeList = makeOuterList(closet, recommendation.getOuters()); - List weatherFeelingDto = makeWeatherFeeling(recommendation.getPredictionMap()); + List weatherFeelingDto = makeWeatherFeeling(recommendation.getPredictionMap(), member); return RecommendPredictDto.builder() .feelingList(weatherFeelingDto) @@ -65,7 +65,7 @@ private RecommendPredictDto makeResultForPredict(Closet closet, ClothingRecommen throw new CustomException(ErrorCode.UNKNOWN_ERROR, "예측 데이터를 변환하는 중 오류가 발생하였습니다: " + e.getMessage()); } } - + // TODO : AI 학습 이후 사용자가 대량으로 의상을 삭제해서 추천할 의상이 없어져버리는 경우에 대한 고민이 필요해보임. private List makeOuterList(Closet closet, List outers) { @@ -105,14 +105,14 @@ private List makeUpperList(Closet closet, List tops) { return new ArrayList<>(resultSet); } - private List makeWeatherFeeling(Map predictionMap) { + private List makeWeatherFeeling(Map predictionMap, Member member) { List feelingList = new ArrayList<>(); LocalDate today = LocalDate.now(); LocalTime now = LocalTime.now(); LocalDate forecastDate = now.isBefore(LocalTime.of(6, 0)) ? today.minusDays(1) : today; - String regionName = "서울특별시 용산구"; + String regionName = (member.getRegionName() != null) ? member.getRegionName() : "서울특별시 용산구"; List hours = predictionMap.keySet().stream() .map(Integer::parseInt) @@ -140,7 +140,6 @@ private List makeWeatherFeeling(Map predicti .feeling(feeling) .temperature(forecast.getTemperature()) .build(); - feelingList.add(dto); } else { throw new CustomException(ErrorCode.WEATHER_DATA_NOT_FOUND); @@ -150,7 +149,6 @@ private List makeWeatherFeeling(Map predicti return feelingList; } - private List getModelPrediction(Long id, LocalDate now) { List list = clothingRecommendationRepository.findByMemberIdAndDate(id, now); if (list.isEmpty()) { From af3577a51b75bf80c196871e3d02c1807c1d8017 Mon Sep 17 00:00:00 2001 From: yeonjae02 <0yeonjae2@naver.com> Date: Fri, 4 Jul 2025 17:10:23 +0900 Subject: [PATCH 23/23] =?UTF-8?q?feat:=20=EC=A7=80=EC=97=AD=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B6=88=EA=B0=80=20=EC=8B=9C=EA=B0=84=EB=8C=80=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/ai_model/service/RecommendationService.java | 1 - .../domain/member/service/AuthService.java | 5 +++++ .../howWeather_backend/global/exception/ErrorCode.java | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java index 87fbc85..60fa746 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/ai_model/service/RecommendationService.java @@ -65,7 +65,6 @@ private RecommendPredictDto makeResultForPredict(Closet closet, ClothingRecommen throw new CustomException(ErrorCode.UNKNOWN_ERROR, "예측 데이터를 변환하는 중 오류가 발생하였습니다: " + e.getMessage()); } } - // TODO : AI 학습 이후 사용자가 대량으로 의상을 삭제해서 추천할 의상이 없어져버리는 경우에 대한 고민이 필요해보임. private List makeOuterList(Closet closet, List outers) { diff --git a/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java b/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java index 9855b79..d2aa3ba 100644 --- a/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java +++ b/src/main/java/com/howWeather/howWeather_backend/domain/member/service/AuthService.java @@ -31,6 +31,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalTime; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; @@ -323,6 +324,10 @@ public String getLoccation(Member member) { @Transactional public void updateLocation(Member member, RegionDto regionDto) { try { + LocalTime now = LocalTime.now(); + if (!now.isBefore(LocalTime.of(4, 00)) && now.isBefore(LocalTime.of(7, 0))) { + throw new CustomException(ErrorCode.TIME_RESTRICTED_FOR_REGION_CHANGE); + } validateRegion(regionDto.getRegionName()); Member persistedMember = memberRepository.findById(member.getId()) diff --git a/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java b/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java index 0aabd64..79797ee 100644 --- a/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java +++ b/src/main/java/com/howWeather/howWeather_backend/global/exception/ErrorCode.java @@ -80,7 +80,8 @@ public enum ErrorCode { OAUTH2_TOKEN_NOT_FOUND("OAuth2 access token을 찾을 수 없습니다.", HttpStatus.UNAUTHORIZED), // 지역 - KAKAO_API_REGION_NOT_FOUND("해당 지역을 찾는 데 실패했습니다.", HttpStatus.NOT_FOUND) + KAKAO_API_REGION_NOT_FOUND("해당 지역을 찾는 데 실패했습니다.", HttpStatus.NOT_FOUND), + TIME_RESTRICTED_FOR_REGION_CHANGE("오전 4:00 ~ 오전 7:00 사이에는 지역 설정을 변경할 수 없습니다.", HttpStatus.BAD_REQUEST) ; private final String message;