mirror of
https://github.com/won-ktds/smarketing-backend.git
synced 2026-06-12 20:39:09 +00:00
add: init project
This commit is contained in:
+1
-4
@@ -1,7 +1,4 @@
|
||||
dependencies {
|
||||
implementation project(':common')
|
||||
}
|
||||
|
||||
bootJar {
|
||||
archiveFileName = "member-service.jar"
|
||||
implementation 'com.mysql:mysql-connector-j'
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.won.smarketing.member.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
/**
|
||||
* JPA 설정 클래스
|
||||
* JPA Auditing 기능 활성화
|
||||
*/
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
public class JpaConfig {
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.won.smarketing.member.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Redis 설정 클래스
|
||||
* Redis 연결 및 RedisTemplate 설정
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Value("${spring.data.redis.host}")
|
||||
private String redisHost;
|
||||
|
||||
@Value("${spring.data.redis.port}")
|
||||
private int redisPort;
|
||||
|
||||
/**
|
||||
* Redis 연결 팩토리 설정
|
||||
*
|
||||
* @return RedisConnectionFactory
|
||||
*/
|
||||
@Bean
|
||||
public RedisConnectionFactory redisConnectionFactory() {
|
||||
return new LettuceConnectionFactory(redisHost, redisPort);
|
||||
}
|
||||
|
||||
/**
|
||||
* RedisTemplate 설정
|
||||
*
|
||||
* @return RedisTemplate<String, String>
|
||||
*/
|
||||
@Bean
|
||||
public RedisTemplate<String, String> redisTemplate() {
|
||||
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory());
|
||||
|
||||
// 직렬화 설정
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setValueSerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
|
||||
|
||||
return redisTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
package com.won.smarketing.member.controller;
|
||||
|
||||
import com.won.smarketing.common.dto.ApiResponse;
|
||||
import com.won.smarketing.member.dto.LoginRequest;
|
||||
import com.won.smarketing.member.dto.LoginResponse;
|
||||
import com.won.smarketing.member.dto.LogoutRequest;
|
||||
import com.won.smarketing.member.dto.TokenRefreshRequest;
|
||||
import com.won.smarketing.member.dto.TokenResponse;
|
||||
import com.won.smarketing.member.dto.*;
|
||||
import com.won.smarketing.member.service.AuthService;
|
||||
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.*;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 인증/인가를 위한 REST API 컨트롤러
|
||||
* 인증을 위한 REST API 컨트롤러
|
||||
* 로그인, 로그아웃, 토큰 갱신 기능 제공
|
||||
*/
|
||||
@Tag(name = "인증/인가", description = "로그인, 로그아웃, 토큰 관리 API")
|
||||
@Tag(name = "인증 관리", description = "로그인, 로그아웃, 토큰 관리 API")
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@RequiredArgsConstructor
|
||||
@@ -28,12 +24,12 @@ public class AuthController {
|
||||
private final AuthService authService;
|
||||
|
||||
/**
|
||||
* 로그인 인증
|
||||
* 로그인
|
||||
*
|
||||
* @param request 로그인 요청 정보
|
||||
* @return JWT 토큰 정보
|
||||
* @return 로그인 성공 응답 (토큰 포함)
|
||||
*/
|
||||
@Operation(summary = "로그인", description = "사용자 인증 후 JWT 토큰을 발급합니다.")
|
||||
@Operation(summary = "로그인", description = "사용자 ID와 패스워드로 로그인합니다.")
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<ApiResponse<LoginResponse>> login(@Valid @RequestBody LoginRequest request) {
|
||||
LoginResponse response = authService.login(request);
|
||||
@@ -41,12 +37,12 @@ public class AuthController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃 처리
|
||||
* 로그아웃
|
||||
*
|
||||
* @param request 로그아웃 요청 정보
|
||||
* @return 로그아웃 성공 응답
|
||||
*/
|
||||
@Operation(summary = "로그아웃", description = "사용자를 로그아웃하고 토큰을 무효화합니다.")
|
||||
@Operation(summary = "로그아웃", description = "리프레시 토큰을 무효화하여 로그아웃합니다.")
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<ApiResponse<Void>> logout(@Valid @RequestBody LogoutRequest request) {
|
||||
authService.logout(request.getRefreshToken());
|
||||
@@ -57,9 +53,9 @@ public class AuthController {
|
||||
* 토큰 갱신
|
||||
*
|
||||
* @param request 토큰 갱신 요청 정보
|
||||
* @return 새로운 JWT 토큰 정보
|
||||
* @return 새로운 토큰 정보
|
||||
*/
|
||||
@Operation(summary = "토큰 갱신", description = "Refresh Token을 사용하여 새로운 Access Token을 발급합니다.")
|
||||
@Operation(summary = "토큰 갱신", description = "리프레시 토큰을 사용하여 새로운 액세스 토큰을 발급받습니다.")
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<ApiResponse<TokenResponse>> refresh(@Valid @RequestBody TokenRefreshRequest request) {
|
||||
TokenResponse response = authService.refresh(request.getRefreshToken());
|
||||
|
||||
@@ -9,15 +9,15 @@ import com.won.smarketing.member.service.MemberService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
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.*;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* 회원 관리를 위한 REST API 컨트롤러
|
||||
* 회원가입, ID 중복 확인, 패스워드 유효성 검증 기능 제공
|
||||
* 회원가입, 중복 확인, 패스워드 검증 기능 제공
|
||||
*/
|
||||
@Tag(name = "회원 관리", description = "회원가입 및 회원 정보 관리 API")
|
||||
@RestController
|
||||
@@ -28,10 +28,10 @@ public class MemberController {
|
||||
private final MemberService memberService;
|
||||
|
||||
/**
|
||||
* 회원가입 처리
|
||||
* 회원가입
|
||||
*
|
||||
* @param request 회원가입 요청 정보
|
||||
* @return 회원가입 성공/실패 응답
|
||||
* @return 회원가입 성공 응답
|
||||
*/
|
||||
@Operation(summary = "회원가입", description = "새로운 회원을 등록합니다.")
|
||||
@PostMapping("/register")
|
||||
@@ -41,34 +41,80 @@ public class MemberController {
|
||||
}
|
||||
|
||||
/**
|
||||
* ID 중복 확인
|
||||
* 사용자 ID 중복 확인
|
||||
*
|
||||
* @param userId 확인할 사용자 ID
|
||||
* @return 중복 여부 응답
|
||||
* @return 중복 확인 결과
|
||||
*/
|
||||
@Operation(summary = "ID 중복 확인", description = "사용자 ID의 중복 여부를 확인합니다.")
|
||||
@GetMapping("/check-duplicate")
|
||||
public ResponseEntity<ApiResponse<DuplicateCheckResponse>> checkDuplicate(
|
||||
@Operation(summary = "사용자 ID 중복 확인", description = "사용자 ID의 중복 여부를 확인합니다.")
|
||||
@GetMapping("/check-duplicate/user-id")
|
||||
public ResponseEntity<ApiResponse<DuplicateCheckResponse>> checkUserIdDuplicate(
|
||||
@Parameter(description = "확인할 사용자 ID", required = true)
|
||||
@RequestParam String userId) {
|
||||
|
||||
boolean isDuplicate = memberService.checkDuplicate(userId);
|
||||
DuplicateCheckResponse response = DuplicateCheckResponse.builder()
|
||||
.isDuplicate(isDuplicate)
|
||||
.message(isDuplicate ? "이미 사용 중인 ID입니다." : "사용 가능한 ID입니다.")
|
||||
.build();
|
||||
DuplicateCheckResponse response = isDuplicate
|
||||
? DuplicateCheckResponse.duplicate("이미 사용 중인 사용자 ID입니다.")
|
||||
: DuplicateCheckResponse.available("사용 가능한 사용자 ID입니다.");
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* 이메일 중복 확인
|
||||
*
|
||||
* @param email 확인할 이메일
|
||||
* @return 중복 확인 결과
|
||||
*/
|
||||
@Operation(summary = "이메일 중복 확인", description = "이메일의 중복 여부를 확인합니다.")
|
||||
@GetMapping("/check-duplicate/email")
|
||||
public ResponseEntity<ApiResponse<DuplicateCheckResponse>> checkEmailDuplicate(
|
||||
@Parameter(description = "확인할 이메일", required = true)
|
||||
@RequestParam String email) {
|
||||
|
||||
boolean isDuplicate = memberService.checkEmailDuplicate(email);
|
||||
DuplicateCheckResponse response = isDuplicate
|
||||
? DuplicateCheckResponse.duplicate("이미 사용 중인 이메일입니다.")
|
||||
: DuplicateCheckResponse.available("사용 가능한 이메일입니다.");
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* 사업자번호 중복 확인
|
||||
*
|
||||
* @param businessNumber 확인할 사업자번호
|
||||
* @return 중복 확인 결과
|
||||
*/
|
||||
@Operation(summary = "사업자번호 중복 확인", description = "사업자번호의 중복 여부를 확인합니다.")
|
||||
@GetMapping("/check-duplicate/business-number")
|
||||
public ResponseEntity<ApiResponse<DuplicateCheckResponse>> checkBusinessNumberDuplicate(
|
||||
@Parameter(description = "확인할 사업자번호", required = true)
|
||||
@RequestParam String businessNumber) {
|
||||
|
||||
boolean isDuplicate = memberService.checkBusinessNumberDuplicate(businessNumber);
|
||||
DuplicateCheckResponse response = isDuplicate
|
||||
? DuplicateCheckResponse.duplicate("이미 등록된 사업자번호입니다.")
|
||||
: DuplicateCheckResponse.available("사용 가능한 사업자번호입니다.");
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* 패스워드 유효성 검증
|
||||
*
|
||||
* @param request 패스워드 유효성 검증 요청
|
||||
* @return 유효성 검증 결과
|
||||
* @param request 패스워드 검증 요청
|
||||
* @return 패스워드 검증 결과
|
||||
*/
|
||||
@Operation(summary = "패스워드 유효성 검증", description = "패스워드가 보안 규칙을 만족하는지 확인합니다.")
|
||||
@Operation(summary = "패스워드 검증", description = "패스워드가 규칙을 만족하는지 확인합니다.")
|
||||
@PostMapping("/validate-password")
|
||||
public ResponseEntity<ApiResponse<ValidationResponse>> validatePassword(@Valid @RequestBody PasswordValidationRequest request) {
|
||||
public ResponseEntity<ApiResponse<ValidationResponse>> validatePassword(
|
||||
@Valid @RequestBody PasswordValidationRequest request) {
|
||||
|
||||
ValidationResponse response = memberService.validatePassword(request.getPassword());
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,19 +7,48 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* ID 중복 확인 응답 DTO
|
||||
* 사용자 ID 중복 여부 확인 결과
|
||||
* 중복 확인 응답 DTO
|
||||
* 사용자 ID, 이메일 등의 중복 확인 결과를 전달합니다.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "ID 중복 확인 응답")
|
||||
@Schema(description = "중복 확인 응답")
|
||||
public class DuplicateCheckResponse {
|
||||
|
||||
|
||||
@Schema(description = "중복 여부", example = "false")
|
||||
private boolean isDuplicate;
|
||||
|
||||
@Schema(description = "확인 결과 메시지", example = "사용 가능한 ID입니다.")
|
||||
|
||||
@Schema(description = "메시지", example = "사용 가능한 ID입니다.")
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 중복된 경우의 응답 생성
|
||||
*
|
||||
* @param message 메시지
|
||||
* @return 중복 응답
|
||||
*/
|
||||
public static DuplicateCheckResponse duplicate(String message) {
|
||||
return DuplicateCheckResponse.builder()
|
||||
.isDuplicate(true)
|
||||
.message(message)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용 가능한 경우의 응답 생성
|
||||
*
|
||||
* @param message 메시지
|
||||
* @return 사용 가능 응답
|
||||
*/
|
||||
public static DuplicateCheckResponse available(String message) {
|
||||
return DuplicateCheckResponse.builder()
|
||||
.isDuplicate(false)
|
||||
.message(message)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
package com.won.smarketing.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 로그인 요청 DTO
|
||||
* 로그인 시 필요한 사용자 ID와 패스워드 정보
|
||||
* 로그인 시 필요한 정보를 전달합니다.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "로그인 요청 정보")
|
||||
@Schema(description = "로그인 요청")
|
||||
public class LoginRequest {
|
||||
|
||||
@Schema(description = "사용자 ID", example = "testuser", required = true)
|
||||
@NotBlank(message = "사용자 ID는 필수입니다.")
|
||||
|
||||
@Schema(description = "사용자 ID", example = "user123", required = true)
|
||||
@NotBlank(message = "사용자 ID는 필수입니다")
|
||||
private String userId;
|
||||
|
||||
|
||||
@Schema(description = "패스워드", example = "password123!", required = true)
|
||||
@NotBlank(message = "패스워드는 필수입니다.")
|
||||
@NotBlank(message = "패스워드는 필수입니다")
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -8,21 +8,40 @@ import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 로그인 응답 DTO
|
||||
* 로그인 성공 시 반환되는 JWT 토큰 정보
|
||||
* 로그인 성공 시 토큰 정보를 전달합니다.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "로그인 응답 정보")
|
||||
@Schema(description = "로그인 응답")
|
||||
public class LoginResponse {
|
||||
|
||||
@Schema(description = "Access Token", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
||||
|
||||
@Schema(description = "액세스 토큰", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
||||
private String accessToken;
|
||||
|
||||
@Schema(description = "Refresh Token", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
||||
|
||||
@Schema(description = "리프레시 토큰", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
||||
private String refreshToken;
|
||||
|
||||
@Schema(description = "토큰 만료 시간 (밀리초)", example = "900000")
|
||||
|
||||
@Schema(description = "토큰 만료 시간 (초)", example = "3600")
|
||||
private long expiresIn;
|
||||
|
||||
@Schema(description = "사용자 정보")
|
||||
private UserInfo userInfo;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "사용자 정보")
|
||||
public static class UserInfo {
|
||||
@Schema(description = "사용자 ID", example = "user123")
|
||||
private String userId;
|
||||
|
||||
@Schema(description = "이름", example = "홍길동")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "이메일", example = "user@example.com")
|
||||
private String email;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
package com.won.smarketing.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 패스워드 유효성 검증 요청 DTO
|
||||
* 패스워드 보안 규칙 확인을 위한 요청 정보
|
||||
* 패스워드 검증 요청 DTO
|
||||
* 패스워드 규칙 검증을 위한 요청 정보를 전달합니다.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "패스워드 유효성 검증 요청")
|
||||
@Schema(description = "패스워드 검증 요청")
|
||||
public class PasswordValidationRequest {
|
||||
|
||||
|
||||
@Schema(description = "검증할 패스워드", example = "password123!", required = true)
|
||||
@NotBlank(message = "패스워드는 필수입니다.")
|
||||
@NotBlank(message = "패스워드는 필수입니다")
|
||||
private String password;
|
||||
}
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
package com.won.smarketing.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.Email;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 회원가입 요청 DTO
|
||||
* 회원가입 시 필요한 정보를 담는 데이터 전송 객체
|
||||
* 회원가입 시 필요한 정보를 전달합니다.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "회원가입 요청 정보")
|
||||
@Schema(description = "회원가입 요청")
|
||||
public class RegisterRequest {
|
||||
|
||||
@Schema(description = "사용자 ID", example = "testuser", required = true)
|
||||
@NotBlank(message = "사용자 ID는 필수입니다.")
|
||||
@Size(min = 4, max = 20, message = "사용자 ID는 4자 이상 20자 이하여야 합니다.")
|
||||
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "사용자 ID는 영문과 숫자만 가능합니다.")
|
||||
|
||||
@Schema(description = "사용자 ID", example = "user123", required = true)
|
||||
@NotBlank(message = "사용자 ID는 필수입니다")
|
||||
@Size(min = 4, max = 20, message = "사용자 ID는 4-20자 사이여야 합니다")
|
||||
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "사용자 ID는 영문과 숫자만 사용 가능합니다")
|
||||
private String userId;
|
||||
|
||||
|
||||
@Schema(description = "패스워드", example = "password123!", required = true)
|
||||
@NotBlank(message = "패스워드는 필수입니다.")
|
||||
@NotBlank(message = "패스워드는 필수입니다")
|
||||
@Size(min = 8, max = 20, message = "패스워드는 8-20자 사이여야 합니다")
|
||||
@Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$",
|
||||
message = "패스워드는 영문, 숫자, 특수문자를 포함해야 합니다")
|
||||
private String password;
|
||||
|
||||
|
||||
@Schema(description = "이름", example = "홍길동", required = true)
|
||||
@NotBlank(message = "이름은 필수입니다.")
|
||||
@Size(max = 100, message = "이름은 100자 이하여야 합니다.")
|
||||
@NotBlank(message = "이름은 필수입니다")
|
||||
@Size(max = 50, message = "이름은 50자 이하여야 합니다")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "사업자 번호", example = "123-45-67890", required = true)
|
||||
@NotBlank(message = "사업자 번호는 필수입니다.")
|
||||
@Pattern(regexp = "^\\d{3}-\\d{2}-\\d{5}$", message = "사업자 번호 형식이 올바르지 않습니다.")
|
||||
|
||||
@Schema(description = "사업자등록번호", example = "123-45-67890")
|
||||
@Pattern(regexp = "^\\d{3}-\\d{2}-\\d{5}$", message = "사업자등록번호 형식이 올바르지 않습니다 (000-00-00000)")
|
||||
private String businessNumber;
|
||||
|
||||
@Schema(description = "이메일", example = "test@example.com", required = true)
|
||||
@NotBlank(message = "이메일은 필수입니다.")
|
||||
@Email(message = "올바른 이메일 형식이 아닙니다.")
|
||||
|
||||
@Schema(description = "이메일", example = "user@example.com", required = true)
|
||||
@NotBlank(message = "이메일은 필수입니다")
|
||||
@Email(message = "이메일 형식이 올바르지 않습니다")
|
||||
@Size(max = 100, message = "이메일은 100자 이하여야 합니다")
|
||||
private String email;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
package com.won.smarketing.member.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 토큰 갱신 요청 DTO
|
||||
* Refresh Token을 사용한 토큰 갱신 요청 정보
|
||||
* 리프레시 토큰을 사용한 액세스 토큰 갱신 요청 정보를 전달합니다.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "토큰 갱신 요청")
|
||||
public class TokenRefreshRequest {
|
||||
|
||||
@Schema(description = "Refresh Token", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", required = true)
|
||||
@NotBlank(message = "Refresh Token은 필수입니다.")
|
||||
|
||||
@Schema(description = "리프레시 토큰", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", required = true)
|
||||
@NotBlank(message = "리프레시 토큰은 필수입니다")
|
||||
private String refreshToken;
|
||||
}
|
||||
|
||||
@@ -8,21 +8,21 @@ import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 토큰 응답 DTO
|
||||
* 토큰 갱신 시 반환되는 새로운 JWT 토큰 정보
|
||||
* 토큰 갱신 시 새로운 토큰 정보를 전달합니다.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "토큰 응답 정보")
|
||||
@Schema(description = "토큰 응답")
|
||||
public class TokenResponse {
|
||||
|
||||
@Schema(description = "새로운 Access Token", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
||||
|
||||
@Schema(description = "새로운 액세스 토큰", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
||||
private String accessToken;
|
||||
|
||||
@Schema(description = "새로운 Refresh Token", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
||||
|
||||
@Schema(description = "새로운 리프레시 토큰", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...")
|
||||
private String refreshToken;
|
||||
|
||||
@Schema(description = "토큰 만료 시간 (밀리초)", example = "900000")
|
||||
|
||||
@Schema(description = "토큰 만료 시간 (초)", example = "3600")
|
||||
private long expiresIn;
|
||||
}
|
||||
|
||||
@@ -9,22 +9,50 @@ import lombok.NoArgsConstructor;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 유효성 검증 응답 DTO
|
||||
* 패스워드 유효성 검증 결과 정보
|
||||
* 검증 응답 DTO
|
||||
* 패스워드 등의 검증 결과를 전달합니다.
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "유효성 검증 응답")
|
||||
@Schema(description = "검증 응답")
|
||||
public class ValidationResponse {
|
||||
|
||||
|
||||
@Schema(description = "유효성 여부", example = "true")
|
||||
private boolean isValid;
|
||||
|
||||
@Schema(description = "검증 결과 메시지", example = "유효한 패스워드입니다.")
|
||||
|
||||
@Schema(description = "메시지", example = "사용 가능한 패스워드입니다.")
|
||||
private String message;
|
||||
|
||||
@Schema(description = "오류 목록")
|
||||
|
||||
@Schema(description = "오류 목록", example = "[\"영문이 포함되어야 합니다\", \"숫자가 포함되어야 합니다\"]")
|
||||
private List<String> errors;
|
||||
|
||||
/**
|
||||
* 유효한 경우의 응답 생성
|
||||
*
|
||||
* @param message 메시지
|
||||
* @return 유효 응답
|
||||
*/
|
||||
public static ValidationResponse valid(String message) {
|
||||
return ValidationResponse.builder()
|
||||
.isValid(true)
|
||||
.message(message)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 유효하지 않은 경우의 응답 생성
|
||||
*
|
||||
* @param message 메시지
|
||||
* @param errors 오류 목록
|
||||
* @return 무효 응답
|
||||
*/
|
||||
public static ValidationResponse invalid(String message, List<String> errors) {
|
||||
return ValidationResponse.builder()
|
||||
.isValid(false)
|
||||
.message(message)
|
||||
.errors(errors)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,87 +1,29 @@
|
||||
package com.won.smarketing.member.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 회원 정보를 나타내는 엔티티
|
||||
* 사용자 ID, 패스워드, 이름, 사업자 번호, 이메일 정보 저장
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "members")
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class Member {
|
||||
|
||||
/**
|
||||
* 회원 고유 식별자
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstruct
|
||||
재시도
|
||||
Y
|
||||
계속
|
||||
편집
|
||||
Member 서비스 모든 클래스 구현
|
||||
코드 ∙ 버전 2
|
||||
/**
|
||||
* 사용자 ID로 회원 조회
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 회원 정보 (Optional)
|
||||
*/
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
Optional<Member> findByUserId(String userId);
|
||||
|
||||
/**
|
||||
* 사용자 ID (로그인용)
|
||||
*/
|
||||
@Column(name = "user_id", unique = true, nullable = false, length = 50)
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 암호화된 패스워드
|
||||
*/
|
||||
@Column(name = "password", nullable = false)
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 회원 이름
|
||||
*/
|
||||
@Column(name = "name", nullable = false, length = 100)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 사업자 번호
|
||||
*/
|
||||
@Column(name = "business_number", unique = true, nullable = false, length = 20)
|
||||
private String businessNumber;
|
||||
|
||||
/**
|
||||
* 이메일 주소
|
||||
*/
|
||||
@Column(name = "email", unique = true, nullable = false)
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 회원 생성 시각
|
||||
*/
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 회원 정보 수정 시각
|
||||
*/
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 엔티티 저장 전 실행되는 메서드
|
||||
* 생성 시각과 수정 시각을 현재 시각으로 설정
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔티티 업데이트 전 실행되는 메서드
|
||||
* 수정 시각을 현재 시각으로 갱신
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
* 사용자 ID 존재 여부 확인
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 존재 여부
|
||||
|
||||
Member 인증 서비스 구현체 및 Controllers
|
||||
코드
|
||||
@@ -17,7 +17,7 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
|
||||
* 사용자 ID로 회원 조회
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 회원 정보
|
||||
* @return 회원 정보 (Optional)
|
||||
*/
|
||||
Optional<Member> findByUserId(String userId);
|
||||
|
||||
@@ -38,9 +38,9 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
|
||||
boolean existsByEmail(String email);
|
||||
|
||||
/**
|
||||
* 사업자 번호 존재 여부 확인
|
||||
* 사업자번호 존재 여부 확인
|
||||
*
|
||||
* @param businessNumber 사업자 번호
|
||||
* @param businessNumber 사업자번호
|
||||
* @return 존재 여부
|
||||
*/
|
||||
boolean existsByBusinessNumber(String businessNumber);
|
||||
|
||||
@@ -5,31 +5,31 @@ import com.won.smarketing.member.dto.LoginResponse;
|
||||
import com.won.smarketing.member.dto.TokenResponse;
|
||||
|
||||
/**
|
||||
* 인증/인가 서비스 인터페이스
|
||||
* 로그인, 로그아웃, 토큰 갱신 기능 정의
|
||||
* 인증 서비스 인터페이스
|
||||
* 로그인, 로그아웃, 토큰 갱신 관련 비즈니스 로직 정의
|
||||
*/
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 로그인 인증 처리
|
||||
* 로그인
|
||||
*
|
||||
* @param request 로그인 요청 정보
|
||||
* @return JWT 토큰 정보
|
||||
* @return 로그인 응답 정보 (토큰 포함)
|
||||
*/
|
||||
LoginResponse login(LoginRequest request);
|
||||
|
||||
/**
|
||||
* 로그아웃 처리
|
||||
* 로그아웃
|
||||
*
|
||||
* @param refreshToken 무효화할 Refresh Token
|
||||
* @param refreshToken 리프레시 토큰
|
||||
*/
|
||||
void logout(String refreshToken);
|
||||
|
||||
/**
|
||||
* 토큰 갱신 처리
|
||||
* 토큰 갱신
|
||||
*
|
||||
* @param refreshToken 갱신에 사용할 Refresh Token
|
||||
* @return 새로운 JWT 토큰 정보
|
||||
* @param refreshToken 리프레시 토큰
|
||||
* @return 새로운 토큰 정보
|
||||
*/
|
||||
TokenResponse refresh(String refreshToken);
|
||||
}
|
||||
|
||||
@@ -2,130 +2,21 @@ package com.won.smarketing.member.service;
|
||||
|
||||
import com.won.smarketing.common.exception.BusinessException;
|
||||
import com.won.smarketing.common.exception.ErrorCode;
|
||||
import com.won.smarketing.common.security.JwtTokenProvider;
|
||||
import com.won.smarketing.member.dto.LoginRequest;
|
||||
import com.won.smarketing.member.dto.LoginResponse;
|
||||
import com.won.smarketing.member.dto.TokenResponse;
|
||||
import com.won.smarketing.member.entity.Member;
|
||||
import com.won.smarketing.member.repository.MemberRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 인증/인가 서비스 구현체
|
||||
* 로그인, 로그아웃, 토큰 갱신 기능 구현
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final MemberRepository memberRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
private static final String REFRESH_TOKEN_PREFIX = "refresh_token:";
|
||||
|
||||
/**
|
||||
* 로그인 인증 처리
|
||||
*
|
||||
* @param request 로그인 요청 정보
|
||||
* @return JWT 토큰 정보
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
// 사용자 조회
|
||||
Member member = memberRepository.findByUserId(request.getUserId())
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND));
|
||||
|
||||
// 패스워드 검증
|
||||
if (!passwordEncoder.matches(request.getPassword(), member.getPassword())) {
|
||||
throw new BusinessException(ErrorCode.INVALID_PASSWORD);
|
||||
}
|
||||
|
||||
// JWT 토큰 생성
|
||||
String accessToken = jwtTokenProvider.generateAccessToken(member.getUserId());
|
||||
String refreshToken = jwtTokenProvider.generateRefreshToken(member.getUserId());
|
||||
long expiresIn = jwtTokenProvider.getAccessTokenValidityTime();
|
||||
|
||||
// Refresh Token을 Redis에 저장
|
||||
String refreshTokenKey = REFRESH_TOKEN_PREFIX + member.getUserId();
|
||||
redisTemplate.opsForValue().set(refreshTokenKey, refreshToken,
|
||||
jwtTokenProvider.getRefreshTokenValidityTime(), TimeUnit.MILLISECONDS);
|
||||
|
||||
return LoginResponse.builder()
|
||||
.accessToken(accessToken)
|
||||
.refreshToken(refreshToken)
|
||||
.expiresIn(expiresIn)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃 처리
|
||||
*
|
||||
* @param refreshToken 무효화할 Refresh Token
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void logout(String refreshToken) {
|
||||
// 토큰에서 사용자 ID 추출
|
||||
String userId = jwtTokenProvider.getUserIdFromToken(refreshToken);
|
||||
import com.
|
||||
재시도
|
||||
Y
|
||||
계속
|
||||
편집
|
||||
Member 인증 서비스 구현체 및 Controllers
|
||||
코드 ∙ 버전 2
|
||||
// 새로운 리프레시 토큰을 Redis에 저장
|
||||
redisTemplate.opsForValue().set(
|
||||
REFRESH_TOKEN_PREFIX + userId,
|
||||
newRefreshToken,
|
||||
7,
|
||||
TimeUnit.DAYS
|
||||
);
|
||||
|
||||
// Redis에서 Refresh Token 삭제
|
||||
String refreshTokenKey = REFRESH_TOKEN_PREFIX + userId;
|
||||
redisTemplate.delete(refreshTokenKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰 갱신 처리
|
||||
*
|
||||
* @param refreshToken 갱신에 사용할 Refresh Token
|
||||
* @return 새로운 JWT 토큰 정보
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public TokenResponse refresh(String refreshToken) {
|
||||
// Refresh Token 유효성 검증
|
||||
if (!jwtTokenProvider.validateToken(refreshToken)) {
|
||||
throw new BusinessException(ErrorCode.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
// 토큰에서 사용자 ID 추출
|
||||
String userId = jwtTokenProvider.getUserIdFromToken(refreshToken);
|
||||
|
||||
// Redis에서 저장된 Refresh Token 확인
|
||||
String refreshTokenKey = REFRESH_TOKEN_PREFIX + userId;
|
||||
String storedRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey);
|
||||
|
||||
if (storedRefreshToken == null || !storedRefreshToken.equals(refreshToken)) {
|
||||
throw new BusinessException(ErrorCode.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
// 사용자 존재 여부 확인
|
||||
Member member = memberRepository.findByUserId(userId)
|
||||
.orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND));
|
||||
|
||||
// 새로운 토큰 생성
|
||||
String newAccessToken = jwtTokenProvider.generateAccessToken(member.getUserId());
|
||||
String newRefreshToken = jwtTokenProvider.generateRefreshToken(member.getUserId());
|
||||
long expiresIn = jwtTokenProvider.getAccessTokenValidityTime();
|
||||
|
||||
// 기존 Refresh Token 삭제 후 새로운 토큰 저장
|
||||
redisTemplate.delete(refreshTokenKey);
|
||||
redisTemplate.opsForValue().set(refreshTokenKey, newRefreshToken,
|
||||
jwtTokenProvider.getRefreshTokenValidityTime(), TimeUnit.MILLISECONDS);
|
||||
|
||||
return TokenResponse.builder()
|
||||
.accessToken(newAccessToken)
|
||||
.refreshToken(newRefreshToken)
|
||||
.expiresIn(expiresIn)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
// 기존 리프레시 토큰을
|
||||
Store 서비스 Entity 및 DTO 클래스들
|
||||
코드
|
||||
@@ -4,13 +4,13 @@ import com.won.smarketing.member.dto.RegisterRequest;
|
||||
import com.won.smarketing.member.dto.ValidationResponse;
|
||||
|
||||
/**
|
||||
* 회원 관리 서비스 인터페이스
|
||||
* 회원가입, 중복 확인, 패스워드 유효성 검증 기능 정의
|
||||
* 회원 서비스 인터페이스
|
||||
* 회원 관리 관련 비즈니스 로직 정의
|
||||
*/
|
||||
public interface MemberService {
|
||||
|
||||
/**
|
||||
* 회원가입 처리
|
||||
* 회원 등록
|
||||
*
|
||||
* @param request 회원가입 요청 정보
|
||||
*/
|
||||
@@ -20,15 +20,31 @@ public interface MemberService {
|
||||
* 사용자 ID 중복 확인
|
||||
*
|
||||
* @param userId 확인할 사용자 ID
|
||||
* @return 중복 여부 (true: 중복, false: 사용 가능)
|
||||
* @return 중복 여부
|
||||
*/
|
||||
boolean checkDuplicate(String userId);
|
||||
|
||||
/**
|
||||
* 이메일 중복 확인
|
||||
*
|
||||
* @param email 확인할 이메일
|
||||
* @return 중복 여부
|
||||
*/
|
||||
boolean checkEmailDuplicate(String email);
|
||||
|
||||
/**
|
||||
* 사업자번호 중복 확인
|
||||
*
|
||||
* @param businessNumber 확인할 사업자번호
|
||||
* @return 중복 여부
|
||||
*/
|
||||
boolean checkBusinessNumberDuplicate(String businessNumber);
|
||||
|
||||
/**
|
||||
* 패스워드 유효성 검증
|
||||
*
|
||||
* @param password 검증할 패스워드
|
||||
* @return 유효성 검증 결과
|
||||
* @return 검증 결과
|
||||
*/
|
||||
ValidationResponse validatePassword(String password);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.won.smarketing.member.dto.ValidationResponse;
|
||||
import com.won.smarketing.member.entity.Member;
|
||||
import com.won.smarketing.member.repository.MemberRepository;
|
||||
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;
|
||||
@@ -16,9 +17,10 @@ import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 회원 관리 서비스 구현체
|
||||
* 회원가입, 중복 확인, 패스워드 유효성 검증 기능 구현
|
||||
* 회원 서비스 구현체
|
||||
* 회원 등록, 중복 확인, 패스워드 검증 기능 구현
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Transactional(readOnly = true)
|
||||
@@ -26,90 +28,119 @@ public class MemberServiceImpl implements MemberService {
|
||||
|
||||
private final MemberRepository memberRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
// 패스워드 정규식: 영문, 숫자, 특수문자 각각 최소 1개 포함, 8자 이상
|
||||
private static final Pattern PASSWORD_PATTERN = Pattern.compile(
|
||||
"^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$"
|
||||
);
|
||||
|
||||
// 패스워드 검증 패턴
|
||||
private static final Pattern LETTER_PATTERN = Pattern.compile(".*[a-zA-Z].*");
|
||||
private static final Pattern DIGIT_PATTERN = Pattern.compile(".*\\d.*");
|
||||
private static final Pattern SPECIAL_CHAR_PATTERN = Pattern.compile(".*[@$!%*?&].*");
|
||||
|
||||
/**
|
||||
* 회원가입 처리
|
||||
* 회원 등록
|
||||
*
|
||||
* @param request 회원가입 요청 정보
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void register(RegisterRequest request) {
|
||||
// 중복 ID 확인
|
||||
log.info("회원 등록 시작: {}", request.getUserId());
|
||||
|
||||
// 중복 확인
|
||||
if (memberRepository.existsByUserId(request.getUserId())) {
|
||||
throw new BusinessException(ErrorCode.DUPLICATE_MEMBER_ID);
|
||||
}
|
||||
|
||||
// 이메일 중복 확인
|
||||
|
||||
if (memberRepository.existsByEmail(request.getEmail())) {
|
||||
throw new BusinessException(ErrorCode.DUPLICATE_EMAIL);
|
||||
}
|
||||
|
||||
// 사업자 번호 중복 확인
|
||||
if (memberRepository.existsByBusinessNumber(request.getBusinessNumber())) {
|
||||
|
||||
if (request.getBusinessNumber() != null &&
|
||||
memberRepository.existsByBusinessNumber(request.getBusinessNumber())) {
|
||||
throw new BusinessException(ErrorCode.DUPLICATE_BUSINESS_NUMBER);
|
||||
}
|
||||
|
||||
// 패스워드 암호화
|
||||
String encodedPassword = passwordEncoder.encode(request.getPassword());
|
||||
|
||||
|
||||
// 회원 엔티티 생성 및 저장
|
||||
Member member = Member.builder()
|
||||
.userId(request.getUserId())
|
||||
.password(encodedPassword)
|
||||
.password(passwordEncoder.encode(request.getPassword()))
|
||||
.name(request.getName())
|
||||
.businessNumber(request.getBusinessNumber())
|
||||
.email(request.getEmail())
|
||||
.build();
|
||||
|
||||
|
||||
memberRepository.save(member);
|
||||
log.info("회원 등록 완료: {}", request.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 ID 중복 확인
|
||||
*
|
||||
* @param userId 확인할 사용자 ID
|
||||
* @return 중복 여부 (true: 중복, false: 사용 가능)
|
||||
* @return 중복 여부
|
||||
*/
|
||||
@Override
|
||||
public boolean checkDuplicate(String userId) {
|
||||
return memberRepository.existsByUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 이메일 중복 확인
|
||||
*
|
||||
* @param email 확인할 이메일
|
||||
* @return 중복 여부
|
||||
*/
|
||||
@Override
|
||||
public boolean checkEmailDuplicate(String email) {
|
||||
return memberRepository.existsByEmail(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* 사업자번호 중복 확인
|
||||
*
|
||||
* @param businessNumber 확인할 사업자번호
|
||||
* @return 중복 여부
|
||||
*/
|
||||
@Override
|
||||
public boolean checkBusinessNumberDuplicate(String businessNumber) {
|
||||
if (businessNumber == null || businessNumber.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return memberRepository.existsByBusinessNumber(businessNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* 패스워드 유효성 검증
|
||||
*
|
||||
* @param password 검증할 패스워드
|
||||
* @return 유효성 검증 결과
|
||||
* @return 검증 결과
|
||||
*/
|
||||
@Override
|
||||
public ValidationResponse validatePassword(String password) {
|
||||
List<String> errors = new ArrayList<>();
|
||||
boolean isValid = true;
|
||||
|
||||
// 길이 검증 (8자 이상)
|
||||
if (password.length() < 8) {
|
||||
errors.add("패스워드는 8자 이상이어야 합니다.");
|
||||
isValid = false;
|
||||
|
||||
// 길이 검증
|
||||
if (password.length() < 8 || password.length() > 20) {
|
||||
errors.add("패스워드는 8-20자 사이여야 합니다");
|
||||
}
|
||||
|
||||
// 패턴 검증 (영문, 숫자, 특수문자 포함)
|
||||
if (!PASSWORD_PATTERN.matcher(password).matches()) {
|
||||
errors.add("패스워드는 영문, 숫자, 특수문자를 각각 최소 1개씩 포함해야 합니다.");
|
||||
isValid = false;
|
||||
|
||||
// 영문 포함 여부
|
||||
if (!LETTER_PATTERN.matcher(password).matches()) {
|
||||
errors.add("영문이 포함되어야 합니다");
|
||||
}
|
||||
|
||||
// 숫자 포함 여부
|
||||
if (!DIGIT_PATTERN.matcher(password).matches()) {
|
||||
errors.add("숫자가 포함되어야 합니다");
|
||||
}
|
||||
|
||||
// 특수문자 포함 여부
|
||||
if (!SPECIAL_CHAR_PATTERN.matcher(password).matches()) {
|
||||
errors.add("특수문자(@$!%*?&)가 포함되어야 합니다");
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
return ValidationResponse.valid("사용 가능한 패스워드입니다.");
|
||||
} else {
|
||||
return ValidationResponse.invalid("패스워드 규칙을 확인해 주세요.", errors);
|
||||
}
|
||||
|
||||
String message = isValid ? "유효한 패스워드입니다." : "패스워드가 보안 규칙을 만족하지 않습니다.";
|
||||
|
||||
return ValidationResponse.builder()
|
||||
.isValid(isValid)
|
||||
.message(message)
|
||||
.errors(errors)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,33 @@
|
||||
server:
|
||||
port: ${SERVER_PORT:8081}
|
||||
servlet:
|
||||
context-path: /
|
||||
port: ${MEMBER_PORT:8081}
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: member-service
|
||||
datasource:
|
||||
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:memberdb}
|
||||
username: ${POSTGRES_USER:postgres}
|
||||
password: ${POSTGRES_PASSWORD:postgres}
|
||||
url: ${DB_URL:jdbc:mysql://localhost:3306/smarketing_member}
|
||||
username: ${DB_USERNAME:root}
|
||||
password: ${DB_PASSWORD:password}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: ${JPA_DDL_AUTO:update}
|
||||
show-sql: ${JPA_SHOW_SQL:true}
|
||||
ddl-auto: ${DDL_AUTO:update}
|
||||
show-sql: ${SHOW_SQL:true}
|
||||
properties:
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.PostgreSQLDialect
|
||||
dialect: org.hibernate.dialect.MySQLDialect
|
||||
format_sql: true
|
||||
data:
|
||||
redis:
|
||||
host: ${REDIS_HOST:localhost}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:}
|
||||
timeout: 2000ms
|
||||
|
||||
jwt:
|
||||
secret-key: ${JWT_SECRET_KEY:mySecretKeyForJWTTokenGenerationThatShouldBeVeryLongAndSecure}
|
||||
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:900000}
|
||||
refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:86400000}
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
operations-sorter: method
|
||||
api-docs:
|
||||
path: /api-docs
|
||||
secret: ${JWT_SECRET:mySecretKeyForJWTTokenGenerationAndValidation123456789}
|
||||
access-token-validity: ${JWT_ACCESS_VALIDITY:3600000}
|
||||
refresh-token-validity: ${JWT_REFRESH_VALIDITY:604800000}
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.won.smarketing.member: ${LOG_LEVEL:DEBUG}
|
||||
com.won.smarketing: ${LOG_LEVEL:DEBUG}
|
||||
|
||||
Reference in New Issue
Block a user