정책에 “찜하기” 기능이 추가됨에 따라 기존 정책 조회 API들(“정책 리스트 조회”, “정책 상세 조회”)의 응답에 사용자의 찜하기 여부를 표시하는 컬럼을 추가하고, 관련 로직을 수정했다. 그리고 정책 조회 API는 인증된 사용자만 접근할 수 있도록 변경되었다.
1. 프로젝트 구조
src/main/java/com/example/withpeace/
│
│
├── config/ # 설정 관련 클래스
│ └── SecurityConfig.java # Spring Security 설정
│
├── constant/
│ └── Constant.java # 상수 정의
│
├── domain/ # 도메인 모델 (엔티티)
│ ├── YouthPolicy.java # 청년 정책 엔티티
│ └── FavoritePolicy.java # 청년 정책 찜하기 엔티티
│
├── repository/ # 데이터 접근 계층
│ ├── YouthPolicyRepository.java # 청년 정책 레포지토리
│ └── FavoritePolicyRepository.java # 정책 찜하기 레포지토리
│
├── dto/ # 데이터 전송 객체
│ └── response/
****│ ├── PolicyListResponseDto.java # 정책 리스트 조회 응답 DTO
│ └── PolicyDetailResponseDto.java # 정책 상세 조회 응답 DTO
│
├── controller/ # 컨트롤러 계층
│ └── PolicyController.java # 정책 관련 API 엔드포인트
│
└── service/ # 비즈니스 로직 계층
└── PolicyService.java # 정책 관련 비즈니스 로직
2. 인증 관련 설정 수정
기존에는 정책 조회 API를 누구나 접근 가능하도록 설정했지만, 찜하기 기능을 추가하게 되면서 인증된 사용자만 접근할 수 있도록 변경되었다.
2.1. Spring Security 설정 파일 구현
SecurityConfig
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
// ... (기존 설정 생략)
@Bean
protected SecurityFilterChain securityFilterChain(final HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(requestMatcherRegistry -> requestMatcherRegistry
// .requestMatchers("/api/v1/policies/**").permitAll() 제거
.anyRequest().authenticated())
// ... (기존 설정 생략)
}
}
“/api/v1/policies/” 경로로 시작하는 모든 접근을 허용하는 코드를 제거하여 기본적으로 인증이 필요하도록 변경했다.
2.2. 상수 정의
Constant
public class Constant {
// ... (다른 상수들)
public static final List<String> NO_NEED_AUTH_URLS = List.of(
// ... (다른 URL들)
// "/api/v1/policies" 제거
);
}
`NO_NEED_AUTH_URLS` 리스트에 있는 경로에 대한 요청은 JWT 인증 필터를 거치지 않는다.
정책 관련 경로("/api/v1/policies")를 제거했다.
3. 레포지토리 구현
FavoritePolicyRepository
public interface FavoritePolicyRepository extends JpaRepository<FavoritePolicy, Long> {
// ... (기존 코드 생략)
boolean existsByUserAndPolicyId(User user, String policyId);
}
`existsByUserAndPolicyId` 메서드는 “정책 리스트 조회”, “정책 상세 조회” 서비스 로직에서 사용할 메서드로, 사용자가 해당 정책을 찜했는지를 확인한다. (찜하기 존재 여부 반환)
4. DTO 수정
찜하기 여부를 표시하기 위해 정책 리스트 조회와 정책 상세 조회 응답 DTO에 `isFavorite` 필드를 추가했다.
4.1. 정책 리스트 조회 응답 DTO
PolicyListResponseDto
@Builder
public record PolicyListResponseDto(
// ... (기존 필드 생략)
boolean isFavorite) {
public static PolicyListResponseDto from(YouthPolicy policy, boolean isFavorite) {
return new PolicyListResponseDto(
// ... (기존 필드 매핑 생략)
isFavorite
);
}
}
`isFavorite` 필드가 찜하기 여부를 표시하며, true 이면 찜한 정책, false 이면 찜하기 않은 정책을 의미한다.
4.2. 정책 상세 조회 응답 DTO
PolicyDetailResponseDto
@Builder
public record PolicyDetailResponseDto(
// ... (기존 필드 생략)
boolean isFavorite
) {
public static PolicyDetailResponseDto from(YouthPolicy policy) {
public static PolicyDetailResponseDto from(YouthPolicy policy, boolean isFavorite) {
return PolicyDetailResponseDto.builder()
// ... (기존 필드 매핑 생략)
.isFavorite(isFavorite)
.build();
}
}
5. 컨트롤러 수정
YouthPolicyController
5.1. 정책 리스트 조회
@GetMapping("")
public ResponseDto<?> getPolicyList(@UserId Long userId,
@RequestParam(defaultValue = "") String region,
@RequestParam(defaultValue = "") String classification,
@RequestParam(defaultValue = "1") @Valid @NotNull @Min(1) Integer pageIndex,
@RequestParam(defaultValue = "10") @Valid @NotNull @Min(10) Integer display) {
List<PolicyListResponseDto> policyList = youthPolicyService.getPolicyList(
userId, region, classification, pageIndex - 1, display);
return ResponseDto.ok(policyList);
}
사용자 ID를 파라미터로 입력받도록 추가했다.
`getPolicyList` 메서드에 사용자 ID를 전달하도록 추가했다.
5.2. 정책 상세 조회
// 정책 상세 조회
@GetMapping("/{policyId}")
public ResponseDto<?> getPolicyDetail(@UserId Long userId, @PathVariable String policyId) {
return ResponseDto.ok(youthPolicyService.getPolicyDetail(userId, policyId));
}
사용자 ID를 파라미터로 입력받도록 추가했다.
getPolicyDetail 메서드에 사용자 ID를 전달하도록 추가했다.
6. 서비스 수정
YouthPolicyService
6.1. 정책 리스트 조회
// 사용자가 특정 정책을 찜했는지 확인
private boolean isFavoritePolicy(User user, String policyId) {
return favoritePolicyRepository.existsByUserAndPolicyId(user, policyId);
}
public List<PolicyListResponseDto> getPolicyList(Long userId, String region, String classification, Integer pageIndex, Integer display) {
// ... (기존 코드 생략)
List<PolicyListResponseDto> policyListResponseDtos = youthPolicyPage.getContent().stream()
.map(policy -> {
// 각 정책에 대해 사용자의 찜하기 여부를 조회
boolean isFavorite = isFavoritePolicy(user, policy.getId());
return PolicyListResponseDto.from(policy, isFavorite);
})
.collect(Collectors.toList());
return policyListResponseDtos;
}
.map(policy -> {
boolean isFavorite = isFavoritePolicy(user, policy.getId());
return PolicyListResponseDto.from(policy, isFavorite);
})
이 부분은 정책 리스트를 순회하면서 각 정책에 대해 현재 사용자의 찜하기 여부를 조회하고, 그 결과를 포함한 DTO를 생성하는 로직이다.
6.2. 정책 상세 조회
// 정책 상세 조회
public PolicyDetailResponseDto getPolicyDetail(Long userId, String policyId) {
User user = getUserById(userId);
YouthPolicy policy = getPolicyById(policyId);
boolean isFavorite = isFavoritePolicy(user, policy.getId());
return PolicyDetailResponseDto.from(policy, isFavorite);
}
7. API 응답 예시
7.1. 정책 리스트 조회
요청 URL
[GET] http://cheongha.site/api/v1/policies?pageIndex=1&display=10®ion&classification
Response Body
{
"data": [
{
"id": "R2024101827221",
"title": "서울시 장애인 전동보장구 수리비 지원",
"introduce": "저소득층에 전동보장구(수동휠체어, 전동휠체어, 전동스쿠터 등) 수리비를 지원하여, 안심 이동권 확대 및 경제적 부담 완화로 장애인 복지서비스 강화",
"classification": "PARTICIPATION_AND_RIGHT",
"region": "서울",
"ageInfo": "제한없음",
"isFavorite": false
},
{
"id": "R2024101827220",
"title": "챗GPT 활용하여 효율적인 마케팅 전략 교육생 모집",
"introduce": "\\"챗GPT 활용하여 효율적인 마케팅 전략\\" 교육생을 다음과 같이 모집합니다.",
"classification": "EDUCATION",
"region": "경기",
"ageInfo": "제한없음",
"isFavorite": true
},
...
],
"error": null
}
7.2. 정책 상세 조회
요청 URL
[GET] http://cheongha.site/api/v1/policies/R2024101827220
Response Body
{
"data": {
"id": "R2024101827220",
"title": "챗GPT 활용하여 효율적인 마케팅 전략 교육생 모집",
"introduce": "\\"챗GPT 활용하여 효율적인 마케팅 전략\\" 교육생을 다음과 같이 모집합니다.",
"classification": "EDUCATION",
"applicationDetails": "1. 교육기간 : 11.4.(월), 11.6(수), 11.7(목).(3회 과정) 13:30~17:30\\n2. 교육장소 : 농업기술센터 3층 전산교육장\\n3. 교육내용 : 챗gpt 활용한 마케팅 전략",
"ageInfo": "제한없음",
"residenceAndIncome": "-",
"education": "제한없음",
"specialization": "제한없음",
"additionalNotes": "1순위: 농업인, 2순위: 챗GPT 교육 희망자(연천군민)",
"participationRestrictions": "-",
"applicationProcess": "방문, 전화 (교육신청서, 첨부파일 다운로드 작성)",
"screeningAndAnnouncement": "-",
"applicationSite": "-",
"submissionDocuments": "-",
"additionalUsefulInformation": "-",
"supervisingAuthority": "-",
"operatingOrganization": "-",
"businessRelatedReferenceSite1": "-",
"businessRelatedReferenceSite2": "-",
"isFavorite": true
},
"error": null
}
'Projects > 청하-청년을 위한 커뮤니티 서비스' 카테고리의 다른 글
[청하] 35. 정책 조회수 기능 구현 (1) | 2024.10.23 |
---|---|
[청하] 34. 정책 찜하기 기능 - 유니크 제약조건 문제 해결 (0) | 2024.10.23 |
[청하] 32. 정책 찜하기 해제 기능 구현 (0) | 2024.10.22 |
[청하] 31. 찜한 정책 리스트 조회 기능 구현 (2) | 2024.10.21 |
[청하] 30. 정책 찜하기 기능 구현 (0) | 2024.10.21 |
댓글