init
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
|
||||
// SMS Service (Optional)
|
||||
implementation 'net.nurigo:sdk:4.3.0'
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.ktds.hi.member;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 회원 관리 서비스 메인 애플리케이션 클래스
|
||||
* 인증, 회원정보 관리, 취향 관리 기능을 제공
|
||||
*
|
||||
* @author 하이오더 개발팀
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@SpringBootApplication(scanBasePackages = {"com.ktds.hi.member", "com.ktds.hi.common"})
|
||||
public class MemberServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MemberServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.ktds.hi.member.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
/**
|
||||
* JPA 설정 클래스
|
||||
*/
|
||||
@Configuration
|
||||
@EnableJpaRepositories(basePackages = "com.ktds.hi.member.repository")
|
||||
public class JpaConfig {
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.ktds.hi.member.config;
|
||||
|
||||
import com.ktds.hi.member.service.JwtTokenProvider;
|
||||
import com.ktds.hi.member.service.AuthService;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* JWT 인증 필터 클래스
|
||||
* 요청 헤더의 JWT 토큰을 검증하고 인증 정보를 설정
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenProvider tokenProvider;
|
||||
private final AuthService authService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
String token = resolveToken(request);
|
||||
|
||||
if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
|
||||
Authentication authentication = tokenProvider.getAuthentication(token);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 요청 헤더에서 JWT 토큰 추출
|
||||
*/
|
||||
private String resolveToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ktds.hi.member.config;
|
||||
|
||||
import com.ktds.hi.member.service.JwtTokenProvider;
|
||||
import com.ktds.hi.member.service.AuthService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* Spring Security 설정 클래스
|
||||
* JWT 기반 인증 및 권한 관리 설정
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final AuthService authService;
|
||||
|
||||
/**
|
||||
* 보안 필터 체인 설정
|
||||
* JWT 인증 방식을 사용하고 세션은 무상태로 관리
|
||||
*/
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers("/api/auth/**", "/api/members/register").permitAll()
|
||||
.requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll()
|
||||
.requestMatchers("/actuator/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, authService),
|
||||
UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 비밀번호 암호화 빈
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 인증 매니저 빈
|
||||
*/
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ktds.hi.member.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Swagger 설정 클래스
|
||||
* API 문서화를 위한 OpenAPI 설정
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
return new OpenAPI()
|
||||
.addServersItem(new Server().url("/"))
|
||||
.info(new Info()
|
||||
.title("하이오더 회원 관리 서비스 API")
|
||||
.description("회원 가입, 로그인, 취향 관리 등 회원 관련 기능을 제공하는 API")
|
||||
.version("1.0.0"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.ktds.hi.member.controller;
|
||||
|
||||
import com.ktds.hi.member.dto.*;
|
||||
import com.ktds.hi.member.service.AuthService;
|
||||
import com.ktds.hi.common.dto.SuccessResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 인증 컨트롤러 클래스
|
||||
* 로그인, 로그아웃, 토큰 갱신 등 인증 관련 API를 제공
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "인증 API", description = "로그인, 로그아웃, 토큰 관리 등 인증 관련 API")
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
/**
|
||||
* 로그인 API
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "로그인", description = "사용자명과 비밀번호로 로그인을 수행합니다.")
|
||||
public ResponseEntity<TokenResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||
TokenResponse response = authService.login(request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃 API
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
@Operation(summary = "로그아웃", description = "현재 로그인된 사용자를 로그아웃 처리합니다.")
|
||||
public ResponseEntity<SuccessResponse> logout(@Valid @RequestBody LogoutRequest request) {
|
||||
authService.logout(request);
|
||||
return ResponseEntity.ok(SuccessResponse.of("로그아웃이 완료되었습니다"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰 갱신 API
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
@Operation(summary = "토큰 갱신", description = "리프레시 토큰을 사용해 새로운 액세스 토큰을 발급받습니다.")
|
||||
public ResponseEntity<TokenResponse> refreshToken(@RequestParam String refreshToken) {
|
||||
TokenResponse response = authService.refreshToken(refreshToken);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 아이디 찾기 API
|
||||
*/
|
||||
@PostMapping("/find-username")
|
||||
@Operation(summary = "아이디 찾기", description = "전화번호를 사용해 아이디를 찾습니다.")
|
||||
public ResponseEntity<SuccessResponse> findUsername(@Valid @RequestBody FindUserIdRequest request) {
|
||||
String username = authService.findUserId(request);
|
||||
return ResponseEntity.ok(SuccessResponse.of("아이디: " + username));
|
||||
}
|
||||
|
||||
/**
|
||||
* 비밀번호 찾기 API
|
||||
*/
|
||||
@PostMapping("/find-password")
|
||||
@Operation(summary = "비밀번호 찾기", description = "전화번호로 임시 비밀번호를 SMS로 발송합니다.")
|
||||
public ResponseEntity<SuccessResponse> findPassword(@Valid @RequestBody FindUserIdRequest request) {
|
||||
authService.findPassword(request);
|
||||
return ResponseEntity.ok(SuccessResponse.of("임시 비밀번호가 SMS로 발송되었습니다"));
|
||||
}
|
||||
|
||||
/**
|
||||
* SMS 인증번호 발송 API
|
||||
*/
|
||||
@PostMapping("/sms/send")
|
||||
@Operation(summary = "SMS 인증번호 발송", description = "입력한 전화번호로 인증번호를 발송합니다.")
|
||||
public ResponseEntity<SuccessResponse> sendSmsVerification(@RequestParam String phone) {
|
||||
authService.sendSmsVerification(phone);
|
||||
return ResponseEntity.ok(SuccessResponse.of("인증번호가 발송되었습니다"));
|
||||
}
|
||||
|
||||
/**
|
||||
* SMS 인증번호 확인 API
|
||||
*/
|
||||
@PostMapping("/sms/verify")
|
||||
@Operation(summary = "SMS 인증번호 확인", description = "입력한 인증번호가 올바른지 확인합니다.")
|
||||
public ResponseEntity<SuccessResponse> verifySmsCode(@RequestParam String phone, @RequestParam String code) {
|
||||
boolean isValid = authService.verifySmsCode(phone, code);
|
||||
|
||||
if (isValid) {
|
||||
return ResponseEntity.ok(SuccessResponse.of("인증이 완료되었습니다"));
|
||||
} else {
|
||||
return ResponseEntity.badRequest().body(SuccessResponse.of("인증번호가 올바르지 않습니다"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.ktds.hi.member.controller;
|
||||
|
||||
import com.ktds.hi.member.dto.*;
|
||||
import com.ktds.hi.member.service.MemberService;
|
||||
import com.ktds.hi.common.dto.SuccessResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 회원 컨트롤러 클래스
|
||||
* 회원 가입, 정보 조회/수정 등 회원 관리 API를 제공
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/members")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "회원 관리 API", description = "회원 가입, 정보 조회/수정 등 회원 관리 관련 API")
|
||||
public class MemberController {
|
||||
|
||||
private final MemberService memberService;
|
||||
|
||||
/**
|
||||
* 회원 가입 API
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
@Operation(summary = "회원 가입", description = "새로운 회원을 등록합니다.")
|
||||
public ResponseEntity<SuccessResponse> registerMember(@Valid @RequestBody SignupRequest request) {
|
||||
Long memberId = memberService.registerMember(request);
|
||||
return ResponseEntity.ok(SuccessResponse.of("회원 가입이 완료되었습니다. 회원ID: " + memberId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 마이페이지 정보 조회 API
|
||||
*/
|
||||
@GetMapping("/profile")
|
||||
@Operation(summary = "마이페이지 조회", description = "현재 로그인한 회원의 정보를 조회합니다.")
|
||||
public ResponseEntity<MyPageResponse> getMyPageInfo(Authentication authentication) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
MyPageResponse response = memberService.getMyPageInfo(memberId);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 닉네임 변경 API
|
||||
*/
|
||||
@PutMapping("/nickname")
|
||||
@Operation(summary = "닉네임 변경", description = "현재 로그인한 회원의 닉네임을 변경합니다.")
|
||||
public ResponseEntity<SuccessResponse> updateNickname(Authentication authentication,
|
||||
@Valid @RequestBody UpdateNicknameRequest request) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
memberService.updateNickname(memberId, request);
|
||||
return ResponseEntity.ok(SuccessResponse.of("닉네임이 변경되었습니다"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 아이디 변경 API
|
||||
*/
|
||||
@PutMapping("/username")
|
||||
@Operation(summary = "아이디 변경", description = "현재 로그인한 회원의 아이디를 변경합니다.")
|
||||
public ResponseEntity<SuccessResponse> updateUsername(Authentication authentication,
|
||||
@RequestParam String username) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
memberService.updateUsername(memberId, username);
|
||||
return ResponseEntity.ok(SuccessResponse.of("아이디가 변경되었습니다"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 비밀번호 변경 API
|
||||
*/
|
||||
@PutMapping("/password")
|
||||
@Operation(summary = "비밀번호 변경", description = "현재 로그인한 회원의 비밀번호를 변경합니다.")
|
||||
public ResponseEntity<SuccessResponse> updatePassword(Authentication authentication,
|
||||
@RequestParam String password) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
memberService.updatePassword(memberId, password);
|
||||
return ResponseEntity.ok(SuccessResponse.of("비밀번호가 변경되었습니다"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 아이디 중복 확인 API
|
||||
*/
|
||||
@GetMapping("/check-username")
|
||||
@Operation(summary = "아이디 중복 확인", description = "사용 가능한 아이디인지 확인합니다.")
|
||||
public ResponseEntity<SuccessResponse> checkUsernameAvailability(@RequestParam String username) {
|
||||
boolean isAvailable = memberService.checkUsernameAvailability(username);
|
||||
String message = isAvailable ? "사용 가능한 아이디입니다" : "이미 사용 중인 아이디입니다";
|
||||
return ResponseEntity.ok(SuccessResponse.of(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 닉네임 중복 확인 API
|
||||
*/
|
||||
@GetMapping("/check-nickname")
|
||||
@Operation(summary = "닉네임 중복 확인", description = "사용 가능한 닉네임인지 확인합니다.")
|
||||
public ResponseEntity<SuccessResponse> checkNicknameAvailability(@RequestParam String nickname) {
|
||||
boolean isAvailable = memberService.checkNicknameAvailability(nickname);
|
||||
String message = isAvailable ? "사용 가능한 닉네임입니다" : "이미 사용 중인 닉네임입니다";
|
||||
return ResponseEntity.ok(SuccessResponse.of(message));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.ktds.hi.member.controller;
|
||||
|
||||
import com.ktds.hi.member.dto.PreferenceRequest;
|
||||
import com.ktds.hi.member.dto.TasteTagResponse;
|
||||
import com.ktds.hi.member.domain.TagType;
|
||||
import com.ktds.hi.member.service.PreferenceService;
|
||||
import com.ktds.hi.common.dto.SuccessResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 취향 관리 컨트롤러 클래스
|
||||
* 취향 정보 등록/수정 및 태그 관리 API를 제공
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/members/preferences")
|
||||
@RequiredArgsConstructor
|
||||
@Tag(name = "취향 관리 API", description = "회원 취향 정보 등록/수정 및 태그 관리 관련 API")
|
||||
public class PreferenceController {
|
||||
|
||||
private final PreferenceService preferenceService;
|
||||
|
||||
/**
|
||||
* 취향 정보 등록/수정 API
|
||||
*/
|
||||
@PostMapping
|
||||
@Operation(summary = "취향 정보 등록", description = "회원의 취향 정보를 등록하거나 수정합니다.")
|
||||
public ResponseEntity<SuccessResponse> savePreference(Authentication authentication,
|
||||
@Valid @RequestBody PreferenceRequest request) {
|
||||
Long memberId = Long.valueOf(authentication.getName());
|
||||
preferenceService.savePreference(memberId, request);
|
||||
return ResponseEntity.ok(SuccessResponse.of("취향 정보가 저장되었습니다"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용 가능한 취향 태그 목록 조회 API
|
||||
*/
|
||||
@GetMapping("/tags")
|
||||
@Operation(summary = "취향 태그 목록 조회", description = "사용 가능한 모든 취향 태그 목록을 조회합니다.")
|
||||
public ResponseEntity<List<TasteTagResponse>> getAvailableTags() {
|
||||
List<TasteTagResponse> tags = preferenceService.getAvailableTags();
|
||||
return ResponseEntity.ok(tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* 태그 유형별 태그 목록 조회 API
|
||||
*/
|
||||
@GetMapping("/tags/by-type")
|
||||
@Operation(summary = "유형별 태그 목록 조회", description = "특정 유형의 취향 태그 목록을 조회합니다.")
|
||||
public ResponseEntity<List<TasteTagResponse>> getTagsByType(@RequestParam TagType tagType) {
|
||||
List<TasteTagResponse> tags = preferenceService.getTagsByType(tagType);
|
||||
return ResponseEntity.ok(tags);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.ktds.hi.member.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 회원 도메인 클래스
|
||||
* 회원의 기본 정보를 담는 도메인 객체
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Member {
|
||||
|
||||
private Long id;
|
||||
private String username;
|
||||
private String password;
|
||||
private String nickname;
|
||||
private String phone;
|
||||
private String role;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 닉네임 변경
|
||||
*/
|
||||
public Member updateNickname(String newNickname) {
|
||||
return Member.builder()
|
||||
.id(this.id)
|
||||
.username(this.username)
|
||||
.password(this.password)
|
||||
.nickname(newNickname)
|
||||
.phone(this.phone)
|
||||
.role(this.role)
|
||||
.createdAt(this.createdAt)
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 아이디 변경
|
||||
*/
|
||||
public Member updateUsername(String newUsername) {
|
||||
return Member.builder()
|
||||
.id(this.id)
|
||||
.username(newUsername)
|
||||
.password(this.password)
|
||||
.nickname(this.nickname)
|
||||
.phone(this.phone)
|
||||
.role(this.role)
|
||||
.createdAt(this.createdAt)
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 비밀번호 변경
|
||||
*/
|
||||
public Member updatePassword(String newPassword) {
|
||||
return Member.builder()
|
||||
.id(this.id)
|
||||
.username(this.username)
|
||||
.password(newPassword)
|
||||
.nickname(this.nickname)
|
||||
.phone(this.phone)
|
||||
.role(this.role)
|
||||
.createdAt(this.createdAt)
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ktds.hi.member.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 취향 정보 도메인 클래스
|
||||
* 회원의 음식 취향 및 건강 정보를 담는 도메인 객체
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Preference {
|
||||
|
||||
private Long id;
|
||||
private Long memberId;
|
||||
private List<String> tags;
|
||||
private String healthInfo;
|
||||
private String spicyLevel;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 취향 정보 업데이트
|
||||
*/
|
||||
public Preference updatePreference(List<String> newTags, String newHealthInfo, String newSpicyLevel) {
|
||||
return Preference.builder()
|
||||
.id(this.id)
|
||||
.memberId(this.memberId)
|
||||
.tags(newTags)
|
||||
.healthInfo(newHealthInfo)
|
||||
.spicyLevel(newSpicyLevel)
|
||||
.createdAt(this.createdAt)
|
||||
.updatedAt(LocalDateTime.now())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ktds.hi.member.domain;
|
||||
|
||||
/**
|
||||
* 태그 유형 열거형
|
||||
* 취향 태그의 카테고리를 정의
|
||||
*/
|
||||
public enum TagType {
|
||||
CUISINE("음식 종류"),
|
||||
FLAVOR("맛"),
|
||||
DIETARY("식이 제한"),
|
||||
ATMOSPHERE("분위기"),
|
||||
PRICE("가격대");
|
||||
|
||||
private final String description;
|
||||
|
||||
TagType(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ktds.hi.member.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 취향 태그 도메인 클래스
|
||||
* 사용 가능한 취향 태그 정보를 담는 도메인 객체
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TasteTag {
|
||||
|
||||
private Long id;
|
||||
private String tagName;
|
||||
private TagType tagType;
|
||||
private String description;
|
||||
private Boolean isActive;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ktds.hi.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 아이디 찾기 요청 DTO
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "아이디 찾기 요청")
|
||||
public class FindUserIdRequest {
|
||||
|
||||
@NotBlank(message = "전화번호는 필수입니다")
|
||||
@Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "전화번호 형식이 올바르지 않습니다")
|
||||
@Schema(description = "전화번호", example = "010-1234-5678")
|
||||
private String phone;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.ktds.hi.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 로그인 요청 DTO
|
||||
* 사용자 로그인 시 필요한 정보를 담는 데이터 전송 객체
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "로그인 요청")
|
||||
public class LoginRequest {
|
||||
|
||||
@NotBlank(message = "사용자명은 필수입니다")
|
||||
@Schema(description = "사용자명", example = "test@example.com")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "비밀번호는 필수입니다")
|
||||
@Schema(description = "비밀번호", example = "password123")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ktds.hi.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 로그아웃 요청 DTO
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "로그아웃 요청")
|
||||
public class LogoutRequest {
|
||||
|
||||
@NotBlank(message = "리프레시 토큰은 필수입니다")
|
||||
@Schema(description = "리프레시 토큰")
|
||||
private String refreshToken;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.ktds.hi.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 마이페이지 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "마이페이지 정보")
|
||||
public class MyPageResponse {
|
||||
|
||||
@Schema(description = "사용자명")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "닉네임")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "전화번호")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "취향 태그 목록")
|
||||
private List<String> preferences;
|
||||
|
||||
@Schema(description = "건강 정보")
|
||||
private String healthInfo;
|
||||
|
||||
@Schema(description = "매운맛 선호도")
|
||||
private String spicyLevel;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ktds.hi.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 취향 정보 등록 요청 DTO
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "취향 정보 등록 요청")
|
||||
public class PreferenceRequest {
|
||||
|
||||
@NotEmpty(message = "취향 태그는 최소 1개 이상 선택해야 합니다")
|
||||
@Schema(description = "취향 태그 목록", example = "[\"한식\", \"매운맛\", \"저칼로리\"]")
|
||||
private List<String> tags;
|
||||
|
||||
@Schema(description = "건강 정보", example = "당뇨 있음")
|
||||
private String healthInfo;
|
||||
|
||||
@Schema(description = "매운맛 선호도", example = "보통")
|
||||
private String spicyLevel;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ktds.hi.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 회원가입 요청 DTO
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "회원가입 요청")
|
||||
public class SignupRequest {
|
||||
|
||||
@NotBlank(message = "사용자명은 필수입니다")
|
||||
@Schema(description = "사용자명", example = "test@example.com")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "비밀번호는 필수입니다")
|
||||
@Size(min = 8, message = "비밀번호는 8자 이상이어야 합니다")
|
||||
@Schema(description = "비밀번호", example = "password123")
|
||||
private String password;
|
||||
|
||||
@NotBlank(message = "닉네임은 필수입니다")
|
||||
@Size(min = 2, max = 20, message = "닉네임은 2-20자 사이여야 합니다")
|
||||
@Schema(description = "닉네임", example = "홍길동")
|
||||
private String nickname;
|
||||
|
||||
@Pattern(regexp = "^010-\\d{4}-\\d{4}$", message = "전화번호 형식이 올바르지 않습니다")
|
||||
@Schema(description = "전화번호", example = "010-1234-5678")
|
||||
private String phone;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ktds.hi.member.dto;
|
||||
|
||||
import com.ktds.hi.member.domain.TagType;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 취향 태그 응답 DTO
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "취향 태그 정보")
|
||||
public class TasteTagResponse {
|
||||
|
||||
@Schema(description = "태그 ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "태그명")
|
||||
private String tagName;
|
||||
|
||||
@Schema(description = "태그 유형")
|
||||
private TagType tagType;
|
||||
|
||||
@Schema(description = "태그 설명")
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ktds.hi.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 토큰 응답 DTO
|
||||
* 로그인 성공 시 반환되는 JWT 토큰 정보
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "토큰 응답")
|
||||
public class TokenResponse {
|
||||
|
||||
@Schema(description = "액세스 토큰")
|
||||
private String accessToken;
|
||||
|
||||
@Schema(description = "리프레시 토큰")
|
||||
private String refreshToken;
|
||||
|
||||
@Schema(description = "회원 ID")
|
||||
private Long memberId;
|
||||
|
||||
@Schema(description = "사용자 역할")
|
||||
private String role;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ktds.hi.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 닉네임 변경 요청 DTO
|
||||
*/
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "닉네임 변경 요청")
|
||||
public class UpdateNicknameRequest {
|
||||
|
||||
@NotBlank(message = "닉네임은 필수입니다")
|
||||
@Size(min = 2, max = 20, message = "닉네임은 2-20자 사이여야 합니다")
|
||||
@Schema(description = "새 닉네임", example = "새닉네임")
|
||||
private String nickname;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.ktds.hi.member.repository.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 회원 엔티티 클래스
|
||||
* 데이터베이스 회원 테이블과 매핑되는 JPA 엔티티
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "members")
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class MemberEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true, nullable = false, length = 100)
|
||||
private String username;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String password;
|
||||
|
||||
@Column(unique = true, nullable = false, length = 50)
|
||||
private String nickname;
|
||||
|
||||
@Column(length = 20)
|
||||
private String phone;
|
||||
|
||||
@Column(length = 20)
|
||||
@Builder.Default
|
||||
private String role = "USER";
|
||||
|
||||
@CreatedDate
|
||||
@Column(updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 닉네임 변경
|
||||
*/
|
||||
public void updateNickname(String newNickname) {
|
||||
this.nickname = newNickname;
|
||||
}
|
||||
|
||||
/**
|
||||
* 아이디 변경
|
||||
*/
|
||||
public void updateUsername(String newUsername) {
|
||||
this.username = newUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* 비밀번호 변경
|
||||
*/
|
||||
public void updatePassword(String newPassword) {
|
||||
this.password = newPassword;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.ktds.hi.member.repository.entity;
|
||||
|
||||
import com.ktds.hi.member.domain.TagType;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 취향 정보 엔티티 클래스
|
||||
* 데이터베이스 preferences 테이블과 매핑되는 JPA 엔티티
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "preferences")
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class PreferenceEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "member_id", nullable = false)
|
||||
private Long memberId;
|
||||
|
||||
@ElementCollection
|
||||
@CollectionTable(name = "preference_tags",
|
||||
joinColumns = @JoinColumn(name = "preference_id"))
|
||||
@Column(name = "tag")
|
||||
private List<String> tags;
|
||||
|
||||
@Column(name = "health_info", length = 500)
|
||||
private String healthInfo;
|
||||
|
||||
@Column(name = "spicy_level", length = 20)
|
||||
private String spicyLevel;
|
||||
|
||||
@CreatedDate
|
||||
@Column(updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 취향 정보 업데이트
|
||||
*/
|
||||
public void updatePreference(List<String> newTags, String newHealthInfo, String newSpicyLevel) {
|
||||
this.tags = newTags;
|
||||
this.healthInfo = newHealthInfo;
|
||||
this.spicyLevel = newSpicyLevel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.ktds.hi.member.repository.entity;
|
||||
|
||||
import com.ktds.hi.member.domain.TagType;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 취향 태그 엔티티 클래스
|
||||
* 데이터베이스 taste_tags 테이블과 매핑되는 JPA 엔티티
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "taste_tags")
|
||||
@Getter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TasteTagEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "tag_name", unique = true, nullable = false, length = 50)
|
||||
private String tagName;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "tag_type", nullable = false)
|
||||
private TagType tagType;
|
||||
|
||||
@Column(length = 200)
|
||||
private String description;
|
||||
|
||||
@Column(name = "is_active")
|
||||
@Builder.Default
|
||||
private Boolean isActive = true;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.ktds.hi.member.repository.jpa;
|
||||
|
||||
import com.ktds.hi.member.repository.entity.MemberEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 회원 JPA 리포지토리 인터페이스
|
||||
* 회원 데이터의 CRUD 작업을 담당
|
||||
*/
|
||||
@Repository
|
||||
public interface MemberRepository extends JpaRepository<MemberEntity, Long> {
|
||||
|
||||
/**
|
||||
* 사용자명으로 회원 조회
|
||||
*/
|
||||
Optional<MemberEntity> findByUsername(String username);
|
||||
|
||||
/**
|
||||
* 닉네임으로 회원 조회
|
||||
*/
|
||||
Optional<MemberEntity> findByNickname(String nickname);
|
||||
|
||||
/**
|
||||
* 전화번호로 회원 조회
|
||||
*/
|
||||
Optional<MemberEntity> findByPhone(String phone);
|
||||
|
||||
/**
|
||||
* 사용자명 존재 여부 확인
|
||||
*/
|
||||
boolean existsByUsername(String username);
|
||||
|
||||
/**
|
||||
* 닉네임 존재 여부 확인
|
||||
*/
|
||||
boolean existsByNickname(String nickname);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.ktds.hi.member.repository.jpa;
|
||||
|
||||
import com.ktds.hi.member.repository.entity.PreferenceEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 취향 정보 JPA 리포지토리 인터페이스
|
||||
* 취향 정보 데이터의 CRUD 작업을 담당
|
||||
*/
|
||||
@Repository
|
||||
public interface PreferenceRepository extends JpaRepository<PreferenceEntity, Long> {
|
||||
|
||||
/**
|
||||
* 회원 ID로 취향 정보 조회
|
||||
*/
|
||||
Optional<PreferenceEntity> findByMemberId(Long memberId);
|
||||
|
||||
/**
|
||||
* 회원 ID로 취향 정보 존재 여부 확인
|
||||
*/
|
||||
boolean existsByMemberId(Long memberId);
|
||||
|
||||
/**
|
||||
* 회원 ID로 취향 정보 삭제
|
||||
*/
|
||||
void deleteByMemberId(Long memberId);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ktds.hi.member.repository.jpa;
|
||||
|
||||
import com.ktds.hi.member.domain.TagType;
|
||||
import com.ktds.hi.member.repository.entity.TasteTagEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 취향 태그 JPA 리포지토리 인터페이스
|
||||
* 취향 태그 데이터의 CRUD 작업을 담당
|
||||
*/
|
||||
@Repository
|
||||
public interface TasteTagRepository extends JpaRepository<TasteTagEntity, Long> {
|
||||
|
||||
/**
|
||||
* 활성화된 태그 목록 조회
|
||||
*/
|
||||
List<TasteTagEntity> findByIsActiveTrue();
|
||||
|
||||
/**
|
||||
* 태그 유형별 태그 목록 조회
|
||||
*/
|
||||
List<TasteTagEntity> findByTagTypeAndIsActiveTrue(TagType tagType);
|
||||
|
||||
/**
|
||||
* 태그명으로 태그 조회
|
||||
*/
|
||||
List<TasteTagEntity> findByTagNameIn(List<String> tagNames);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ktds.hi.member.service;
|
||||
|
||||
import com.ktds.hi.member.dto.*;
|
||||
|
||||
/**
|
||||
* 인증 서비스 인터페이스
|
||||
* 로그인, 로그아웃, 토큰 관리 등 인증 관련 기능을 정의
|
||||
*/
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 로그인 처리
|
||||
*/
|
||||
TokenResponse login(LoginRequest request);
|
||||
|
||||
/**
|
||||
* 로그아웃 처리
|
||||
*/
|
||||
void logout(LogoutRequest request);
|
||||
|
||||
/**
|
||||
* 토큰 갱신
|
||||
*/
|
||||
TokenResponse refreshToken(String refreshToken);
|
||||
|
||||
/**
|
||||
* 아이디 찾기
|
||||
*/
|
||||
String findUserId(FindUserIdRequest request);
|
||||
|
||||
/**
|
||||
* 비밀번호 찾기 (SMS 발송)
|
||||
*/
|
||||
void findPassword(FindUserIdRequest request);
|
||||
|
||||
/**
|
||||
* SMS 인증번호 발송
|
||||
*/
|
||||
void sendSmsVerification(String phone);
|
||||
|
||||
/**
|
||||
* SMS 인증번호 확인
|
||||
*/
|
||||
boolean verifySmsCode(String phone, String code);
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package com.ktds.hi.member.service;
|
||||
|
||||
import com.ktds.hi.member.dto.*;
|
||||
import com.ktds.hi.member.repository.entity.MemberEntity;
|
||||
import com.ktds.hi.member.repository.jpa.MemberRepository;
|
||||
import com.ktds.hi.common.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 인증 서비스 구현체
|
||||
* 로그인, 로그아웃, 토큰 관리 등 인증 관련 기능을 구현
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final MemberRepository memberRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final SmsService smsService;
|
||||
private final RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
@Override
|
||||
public TokenResponse login(LoginRequest request) {
|
||||
// 회원 조회
|
||||
MemberEntity member = memberRepository.findByUsername(request.getUsername())
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 사용자입니다"));
|
||||
|
||||
// 비밀번호 검증
|
||||
if (!passwordEncoder.matches(request.getPassword(), member.getPassword())) {
|
||||
throw new BusinessException("비밀번호가 일치하지 않습니다");
|
||||
}
|
||||
|
||||
// JWT 토큰 생성
|
||||
String accessToken = jwtTokenProvider.generateAccessToken(member.getId(), member.getRole());
|
||||
String refreshToken = jwtTokenProvider.generateRefreshToken(member.getId());
|
||||
|
||||
// 리프레시 토큰 Redis 저장
|
||||
redisTemplate.opsForValue().set(
|
||||
"refresh_token:" + member.getId(),
|
||||
refreshToken,
|
||||
7, TimeUnit.DAYS
|
||||
);
|
||||
|
||||
return TokenResponse.builder()
|
||||
.accessToken(accessToken)
|
||||
.refreshToken(refreshToken)
|
||||
.memberId(member.getId())
|
||||
.role(member.getRole())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(LogoutRequest request) {
|
||||
// 리프레시 토큰에서 사용자 ID 추출
|
||||
Long memberId = jwtTokenProvider.getMemberIdFromToken(request.getRefreshToken());
|
||||
|
||||
// Redis에서 리프레시 토큰 삭제
|
||||
redisTemplate.delete("refresh_token:" + memberId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TokenResponse refreshToken(String refreshToken) {
|
||||
// 토큰 유효성 검증
|
||||
if (!jwtTokenProvider.validateToken(refreshToken)) {
|
||||
throw new BusinessException("유효하지 않은 리프레시 토큰입니다");
|
||||
}
|
||||
|
||||
Long memberId = jwtTokenProvider.getMemberIdFromToken(refreshToken);
|
||||
|
||||
// Redis에서 리프레시 토큰 확인
|
||||
String storedToken = redisTemplate.opsForValue().get("refresh_token:" + memberId);
|
||||
if (!refreshToken.equals(storedToken)) {
|
||||
throw new BusinessException("유효하지 않은 리프레시 토큰입니다");
|
||||
}
|
||||
|
||||
// 회원 정보 조회
|
||||
MemberEntity member = memberRepository.findById(memberId)
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 사용자입니다"));
|
||||
|
||||
// 새 토큰 생성
|
||||
String newAccessToken = jwtTokenProvider.generateAccessToken(member.getId(), member.getRole());
|
||||
String newRefreshToken = jwtTokenProvider.generateRefreshToken(member.getId());
|
||||
|
||||
// 새 리프레시 토큰 Redis 저장
|
||||
redisTemplate.opsForValue().set(
|
||||
"refresh_token:" + member.getId(),
|
||||
newRefreshToken,
|
||||
7, TimeUnit.DAYS
|
||||
);
|
||||
|
||||
return TokenResponse.builder()
|
||||
.accessToken(newAccessToken)
|
||||
.refreshToken(newRefreshToken)
|
||||
.memberId(member.getId())
|
||||
.role(member.getRole())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findUserId(FindUserIdRequest request) {
|
||||
MemberEntity member = memberRepository.findByPhone(request.getPhone())
|
||||
.orElseThrow(() -> new BusinessException("해당 전화번호로 가입된 계정이 없습니다"));
|
||||
|
||||
return member.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findPassword(FindUserIdRequest request) {
|
||||
MemberEntity member = memberRepository.findByPhone(request.getPhone())
|
||||
.orElseThrow(() -> new BusinessException("해당 전화번호로 가입된 계정이 없습니다"));
|
||||
|
||||
// 임시 비밀번호 생성 및 SMS 발송
|
||||
String tempPassword = generateTempPassword();
|
||||
smsService.sendTempPassword(request.getPhone(), tempPassword);
|
||||
|
||||
// 임시 비밀번호로 업데이트
|
||||
member.updatePassword(passwordEncoder.encode(tempPassword));
|
||||
memberRepository.save(member);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendSmsVerification(String phone) {
|
||||
String verificationCode = generateVerificationCode();
|
||||
|
||||
// SMS 발송
|
||||
smsService.sendVerificationCode(phone, verificationCode);
|
||||
|
||||
// Redis에 인증코드 저장 (5분 만료)
|
||||
redisTemplate.opsForValue().set(
|
||||
"sms_code:" + phone,
|
||||
verificationCode,
|
||||
5, TimeUnit.MINUTES
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifySmsCode(String phone, String code) {
|
||||
String storedCode = redisTemplate.opsForValue().get("sms_code:" + phone);
|
||||
|
||||
if (storedCode != null && storedCode.equals(code)) {
|
||||
// 인증 성공 시 코드 삭제
|
||||
redisTemplate.delete("sms_code:" + phone);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 임시 비밀번호 생성
|
||||
*/
|
||||
private String generateTempPassword() {
|
||||
return "temp" + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* SMS 인증코드 생성
|
||||
*/
|
||||
private String generateVerificationCode() {
|
||||
return String.valueOf((int)(Math.random() * 900000) + 100000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.ktds.hi.member.service;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* JWT 토큰 프로바이더 클래스
|
||||
* JWT 토큰 생성, 검증, 파싱 기능을 제공
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class JwtTokenProvider {
|
||||
|
||||
private final SecretKey secretKey;
|
||||
private final long accessTokenExpiration;
|
||||
private final long refreshTokenExpiration;
|
||||
|
||||
public JwtTokenProvider(@Value("${jwt.secret}") String secret,
|
||||
@Value("${jwt.access-token-expiration}") long accessTokenExpiration,
|
||||
@Value("${jwt.refresh-token-expiration}") long refreshTokenExpiration) {
|
||||
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
|
||||
this.accessTokenExpiration = accessTokenExpiration;
|
||||
this.refreshTokenExpiration = refreshTokenExpiration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 액세스 토큰 생성
|
||||
*/
|
||||
public String generateAccessToken(Long memberId, String role) {
|
||||
Date now = new Date();
|
||||
Date expiration = new Date(now.getTime() + accessTokenExpiration);
|
||||
|
||||
return Jwts.builder()
|
||||
.setSubject(memberId.toString())
|
||||
.claim("role", role)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiration)
|
||||
.signWith(secretKey)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 리프레시 토큰 생성
|
||||
*/
|
||||
public String generateRefreshToken(Long memberId) {
|
||||
Date now = new Date();
|
||||
Date expiration = new Date(now.getTime() + refreshTokenExpiration);
|
||||
|
||||
return Jwts.builder()
|
||||
.setSubject(memberId.toString())
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiration)
|
||||
.signWith(secretKey)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰에서 인증 정보 추출
|
||||
*/
|
||||
public Authentication getAuthentication(String token) {
|
||||
Claims claims = parseClaims(token);
|
||||
|
||||
String memberId = claims.getSubject();
|
||||
String role = claims.get("role", String.class);
|
||||
|
||||
return new UsernamePasswordAuthenticationToken(
|
||||
memberId,
|
||||
null,
|
||||
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰에서 회원 ID 추출
|
||||
*/
|
||||
public Long getMemberIdFromToken(String token) {
|
||||
Claims claims = parseClaims(token);
|
||||
return Long.valueOf(claims.getSubject());
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰 유효성 검증
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
parseClaims(token);
|
||||
return true;
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
log.warn("Invalid JWT token: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰 파싱
|
||||
*/
|
||||
private Claims parseClaims(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ktds.hi.member.service;
|
||||
|
||||
import com.ktds.hi.member.dto.*;
|
||||
|
||||
/**
|
||||
* 회원 서비스 인터페이스
|
||||
* 회원 가입, 정보 조회/수정 등 회원 관리 기능을 정의
|
||||
*/
|
||||
public interface MemberService {
|
||||
|
||||
/**
|
||||
* 회원 가입
|
||||
*/
|
||||
Long registerMember(SignupRequest request);
|
||||
|
||||
/**
|
||||
* 마이페이지 정보 조회
|
||||
*/
|
||||
MyPageResponse getMyPageInfo(Long memberId);
|
||||
|
||||
/**
|
||||
* 닉네임 변경
|
||||
*/
|
||||
void updateNickname(Long memberId, UpdateNicknameRequest request);
|
||||
|
||||
/**
|
||||
* 아이디 변경
|
||||
*/
|
||||
void updateUsername(Long memberId, String newUsername);
|
||||
|
||||
/**
|
||||
* 비밀번호 변경
|
||||
*/
|
||||
void updatePassword(Long memberId, String newPassword);
|
||||
|
||||
/**
|
||||
* 아이디 중복 확인
|
||||
*/
|
||||
boolean checkUsernameAvailability(String username);
|
||||
|
||||
/**
|
||||
* 닉네임 중복 확인
|
||||
*/
|
||||
boolean checkNicknameAvailability(String nickname);
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.ktds.hi.member.service;
|
||||
|
||||
import com.ktds.hi.member.dto.*;
|
||||
import com.ktds.hi.member.repository.entity.MemberEntity;
|
||||
import com.ktds.hi.member.repository.entity.PreferenceEntity;
|
||||
import com.ktds.hi.member.repository.jpa.MemberRepository;
|
||||
import com.ktds.hi.member.repository.jpa.PreferenceRepository;
|
||||
import com.ktds.hi.common.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* 회원 서비스 구현체
|
||||
* 회원 가입, 정보 조회/수정 등 회원 관리 기능을 구현
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class MemberServiceImpl implements MemberService {
|
||||
|
||||
private final MemberRepository memberRepository;
|
||||
private final PreferenceRepository preferenceRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public Long registerMember(SignupRequest request) {
|
||||
// 중복 검사
|
||||
if (memberRepository.existsByUsername(request.getUsername())) {
|
||||
throw new BusinessException("이미 사용 중인 사용자명입니다");
|
||||
}
|
||||
|
||||
if (memberRepository.existsByNickname(request.getNickname())) {
|
||||
throw new BusinessException("이미 사용 중인 닉네임입니다");
|
||||
}
|
||||
|
||||
// 회원 생성
|
||||
MemberEntity member = MemberEntity.builder()
|
||||
.username(request.getUsername())
|
||||
.password(passwordEncoder.encode(request.getPassword()))
|
||||
.nickname(request.getNickname())
|
||||
.phone(request.getPhone())
|
||||
.role("USER")
|
||||
.build();
|
||||
|
||||
MemberEntity savedMember = memberRepository.save(member);
|
||||
|
||||
log.info("회원 가입 완료: memberId={}, username={}", savedMember.getId(), savedMember.getUsername());
|
||||
|
||||
return savedMember.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public MyPageResponse getMyPageInfo(Long memberId) {
|
||||
MemberEntity member = memberRepository.findById(memberId)
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 회원입니다"));
|
||||
|
||||
PreferenceEntity preference = preferenceRepository.findByMemberId(memberId)
|
||||
.orElse(null);
|
||||
|
||||
return MyPageResponse.builder()
|
||||
.username(member.getUsername())
|
||||
.nickname(member.getNickname())
|
||||
.phone(member.getPhone())
|
||||
.preferences(preference != null ? preference.getTags() : Collections.emptyList())
|
||||
.healthInfo(preference != null ? preference.getHealthInfo() : null)
|
||||
.spicyLevel(preference != null ? preference.getSpicyLevel() : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateNickname(Long memberId, UpdateNicknameRequest request) {
|
||||
MemberEntity member = memberRepository.findById(memberId)
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 회원입니다"));
|
||||
|
||||
// 닉네임 중복 검사
|
||||
if (memberRepository.existsByNickname(request.getNickname())) {
|
||||
throw new BusinessException("이미 사용 중인 닉네임입니다");
|
||||
}
|
||||
|
||||
member.updateNickname(request.getNickname());
|
||||
memberRepository.save(member);
|
||||
|
||||
log.info("닉네임 변경 완료: memberId={}, newNickname={}", memberId, request.getNickname());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUsername(Long memberId, String newUsername) {
|
||||
MemberEntity member = memberRepository.findById(memberId)
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 회원입니다"));
|
||||
|
||||
// 아이디 중복 검사
|
||||
if (memberRepository.existsByUsername(newUsername)) {
|
||||
throw new BusinessException("이미 사용 중인 사용자명입니다");
|
||||
}
|
||||
|
||||
member.updateUsername(newUsername);
|
||||
memberRepository.save(member);
|
||||
|
||||
log.info("아이디 변경 완료: memberId={}, newUsername={}", memberId, newUsername);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePassword(Long memberId, String newPassword) {
|
||||
MemberEntity member = memberRepository.findById(memberId)
|
||||
.orElseThrow(() -> new BusinessException("존재하지 않는 회원입니다"));
|
||||
|
||||
member.updatePassword(passwordEncoder.encode(newPassword));
|
||||
memberRepository.save(member);
|
||||
|
||||
log.info("비밀번호 변경 완료: memberId={}", memberId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public boolean checkUsernameAvailability(String username) {
|
||||
return !memberRepository.existsByUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public boolean checkNicknameAvailability(String nickname) {
|
||||
return !memberRepository.existsByNickname(nickname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ktds.hi.member.service;
|
||||
|
||||
import com.ktds.hi.member.dto.PreferenceRequest;
|
||||
import com.ktds.hi.member.dto.TasteTagResponse;
|
||||
import com.ktds.hi.member.domain.TagType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 취향 관리 서비스 인터페이스
|
||||
* 취향 정보 등록/수정 및 태그 관리 기능을 정의
|
||||
*/
|
||||
public interface PreferenceService {
|
||||
|
||||
/**
|
||||
* 취향 정보 등록/수정
|
||||
*/
|
||||
void savePreference(Long memberId, PreferenceRequest request);
|
||||
|
||||
/**
|
||||
* 사용 가능한 취향 태그 목록 조회
|
||||
*/
|
||||
List<TasteTagResponse> getAvailableTags();
|
||||
|
||||
/**
|
||||
* 태그 유형별 태그 목록 조회
|
||||
*/
|
||||
List<TasteTagResponse> getTagsByType(TagType tagType);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.ktds.hi.member.service;
|
||||
|
||||
import com.ktds.hi.member.dto.PreferenceRequest;
|
||||
import com.ktds.hi.member.dto.TasteTagResponse;
|
||||
import com.ktds.hi.member.domain.TagType;
|
||||
import com.ktds.hi.member.repository.entity.PreferenceEntity;
|
||||
import com.ktds.hi.member.repository.entity.TasteTagEntity;
|
||||
import com.ktds.hi.member.repository.jpa.PreferenceRepository;
|
||||
import com.ktds.hi.member.repository.jpa.TasteTagRepository;
|
||||
import com.ktds.hi.common.exception.BusinessException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 취향 관리 서비스 구현체
|
||||
* 취향 정보 등록/수정 및 태그 관리 기능을 구현
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class PreferenceServiceImpl implements PreferenceService {
|
||||
|
||||
private final PreferenceRepository preferenceRepository;
|
||||
private final TasteTagRepository tasteTagRepository;
|
||||
|
||||
@Override
|
||||
public void savePreference(Long memberId, PreferenceRequest request) {
|
||||
// 태그 유효성 검증
|
||||
List<TasteTagEntity> existingTags = tasteTagRepository.findByTagNameIn(request.getTags());
|
||||
if (existingTags.size() != request.getTags().size()) {
|
||||
throw new BusinessException("유효하지 않은 태그가 포함되어 있습니다");
|
||||
}
|
||||
|
||||
// 기존 취향 정보 조회
|
||||
PreferenceEntity preference = preferenceRepository.findByMemberId(memberId)
|
||||
.orElse(null);
|
||||
|
||||
if (preference != null) {
|
||||
// 기존 정보 업데이트
|
||||
preference.updatePreference(request.getTags(), request.getHealthInfo(), request.getSpicyLevel());
|
||||
} else {
|
||||
// 새로운 취향 정보 생성
|
||||
preference = PreferenceEntity.builder()
|
||||
.memberId(memberId)
|
||||
.tags(request.getTags())
|
||||
.healthInfo(request.getHealthInfo())
|
||||
.spicyLevel(request.getSpicyLevel())
|
||||
.build();
|
||||
}
|
||||
|
||||
preferenceRepository.save(preference);
|
||||
|
||||
log.info("취향 정보 저장 완료: memberId={}, tags={}", memberId, request.getTags());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<TasteTagResponse> getAvailableTags() {
|
||||
List<TasteTagEntity> tags = tasteTagRepository.findByIsActiveTrue();
|
||||
|
||||
return tags.stream()
|
||||
.map(tag -> TasteTagResponse.builder()
|
||||
.id(tag.getId())
|
||||
.tagName(tag.getTagName())
|
||||
.tagType(tag.getTagType())
|
||||
.description(tag.getDescription())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public List<TasteTagResponse> getTagsByType(TagType tagType) {
|
||||
List<TasteTagEntity> tags = tasteTagRepository.findByTagTypeAndIsActiveTrue(tagType);
|
||||
|
||||
return tags.stream()
|
||||
.map(tag -> TasteTagResponse.builder()
|
||||
.id(tag.getId())
|
||||
.tagName(tag.getTagName())
|
||||
.tagType(tag.getTagType())
|
||||
.description(tag.getDescription())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.ktds.hi.member.service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* SMS 서비스 클래스
|
||||
* SMS 발송 기능을 담당 (실제 구현은 외부 SMS API 연동 필요)
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SmsService {
|
||||
|
||||
@Value("${sms.api-key:}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${sms.api-secret:}")
|
||||
private String apiSecret;
|
||||
|
||||
@Value("${sms.from-number:}")
|
||||
private String fromNumber;
|
||||
|
||||
/**
|
||||
* SMS 인증번호 발송
|
||||
*/
|
||||
public void sendVerificationCode(String phone, String code) {
|
||||
String message = String.format("[하이오더] 인증번호는 %s입니다.", code);
|
||||
|
||||
// TODO: 실제 SMS API 연동 구현
|
||||
log.info("SMS 발송: phone={}, message={}", phone, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 임시 비밀번호 SMS 발송
|
||||
*/
|
||||
public void sendTempPassword(String phone, String tempPassword) {
|
||||
String message = String.format("[하이오더] 임시 비밀번호는 %s입니다. 로그인 후 비밀번호를 변경해주세요.", tempPassword);
|
||||
|
||||
// TODO: 실제 SMS API 연동 구현
|
||||
log.info("임시 비밀번호 SMS 발송: phone={}, tempPassword={}", phone, tempPassword);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
server:
|
||||
port: ${MEMBER_SERVICE_PORT:8081}
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: member-service
|
||||
|
||||
datasource:
|
||||
url: ${MEMBER_DB_URL:jdbc:postgresql://localhost:5432/hiorder_member}
|
||||
username: ${MEMBER_DB_USERNAME:hiorder_user}
|
||||
password: ${MEMBER_DB_PASSWORD:hiorder_pass}
|
||||
driver-class-name: org.postgresql.Driver
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: ${JPA_DDL_AUTO:validate}
|
||||
show-sql: ${JPA_SHOW_SQL:false}
|
||||
properties:
|
||||
hibernate:
|
||||
format_sql: true
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
timeout: 2000ms
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-wait: -1ms
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
|
||||
jwt:
|
||||
secret: ${JWT_SECRET:hiorder-secret-key-for-jwt-token-generation-must-be-long-enough}
|
||||
access-token-expiration: ${JWT_ACCESS_EXPIRATION:3600000} # 1시간
|
||||
refresh-token-expiration: ${JWT_REFRESH_EXPIRATION:604800000} # 7일
|
||||
|
||||
sms:
|
||||
api-key: ${SMS_API_KEY:}
|
||||
api-secret: ${SMS_API_SECRET:}
|
||||
from-number: ${SMS_FROM_NUMBER:}
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health,info,metrics
|
||||
Reference in New Issue
Block a user