mirror of
https://github.com/won-ktds/smarketing-backend.git
synced 2025-12-06 07:06:24 +00:00
fix: build
This commit is contained in:
parent
f6d4380dc7
commit
38af15a3fd
@ -38,7 +38,7 @@ public class WeatherApiDataProvider implements WeatherDataProvider {
|
||||
* @return 날씨 데이터
|
||||
*/
|
||||
@Override
|
||||
public WeatherData getCurrentWeather(String location) {
|
||||
public WeatherApiResponse getCurrentWeather(String location) {
|
||||
try {
|
||||
log.debug("날씨 정보 조회 시작: location={}", location);
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* AI 마케팅 추천을 위한 REST API 컨트롤러
|
||||
|
||||
@ -2,6 +2,7 @@ package com.won.smarketing.recommend.presentation.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@ -14,6 +15,7 @@ import java.time.LocalDateTime;
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Schema(description = "AI 마케팅 팁 생성 응답")
|
||||
public class MarketingTipResponse {
|
||||
|
||||
|
||||
@ -18,12 +18,6 @@ subprojects {
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
@ -32,6 +26,7 @@ subprojects {
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
|
||||
@ -2,6 +2,7 @@ package com.won.smarketing.common.security;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -18,19 +19,26 @@ import java.util.Date;
|
||||
public class JwtTokenProvider {
|
||||
|
||||
private final SecretKey secretKey;
|
||||
/**
|
||||
* -- GETTER --
|
||||
* 액세스 토큰 유효시간 반환
|
||||
*
|
||||
* @return 액세스 토큰 유효시간 (밀리초)
|
||||
*/
|
||||
@Getter
|
||||
private final long accessTokenValidityTime;
|
||||
private final long refreshTokenValidityTime;
|
||||
|
||||
/**
|
||||
* JWT 토큰 프로바이더 생성자
|
||||
*
|
||||
*
|
||||
* @param secret JWT 서명에 사용할 비밀키
|
||||
* @param accessTokenValidityTime 액세스 토큰 유효시간 (밀리초)
|
||||
* @param refreshTokenValidityTime 리프레시 토큰 유효시간 (밀리초)
|
||||
*/
|
||||
public JwtTokenProvider(@Value("${jwt.secret}") String secret,
|
||||
@Value("${jwt.access-token-validity}") long accessTokenValidityTime,
|
||||
@Value("${jwt.refresh-token-validity}") long refreshTokenValidityTime) {
|
||||
@Value("${jwt.access-token-validity}") long accessTokenValidityTime,
|
||||
@Value("${jwt.refresh-token-validity}") long refreshTokenValidityTime) {
|
||||
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
|
||||
this.accessTokenValidityTime = accessTokenValidityTime;
|
||||
this.refreshTokenValidityTime = refreshTokenValidityTime;
|
||||
@ -38,7 +46,7 @@ public class JwtTokenProvider {
|
||||
|
||||
/**
|
||||
* 액세스 토큰 생성
|
||||
*
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 생성된 액세스 토큰
|
||||
*/
|
||||
@ -47,16 +55,16 @@ public class JwtTokenProvider {
|
||||
Date expiryDate = new Date(now.getTime() + accessTokenValidityTime);
|
||||
|
||||
return Jwts.builder()
|
||||
.setSubject(userId)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiryDate)
|
||||
.subject(userId)
|
||||
.issuedAt(now)
|
||||
.expiration(expiryDate)
|
||||
.signWith(secretKey)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 리프레시 토큰 생성
|
||||
*
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 생성된 리프레시 토큰
|
||||
*/
|
||||
@ -65,41 +73,41 @@ public class JwtTokenProvider {
|
||||
Date expiryDate = new Date(now.getTime() + refreshTokenValidityTime);
|
||||
|
||||
return Jwts.builder()
|
||||
.setSubject(userId)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(expiryDate)
|
||||
.subject(userId)
|
||||
.issuedAt(now)
|
||||
.expiration(expiryDate)
|
||||
.signWith(secretKey)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰에서 사용자 ID 추출
|
||||
*
|
||||
*
|
||||
* @param token JWT 토큰
|
||||
* @return 사용자 ID
|
||||
*/
|
||||
public String getUserIdFromToken(String token) {
|
||||
Claims claims = Jwts.parserBuilder()
|
||||
.setSigningKey(secretKey)
|
||||
Claims claims = Jwts.parser()
|
||||
.verifyWith(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰 유효성 검증
|
||||
*
|
||||
*
|
||||
* @param token 검증할 토큰
|
||||
* @return 유효성 여부
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parserBuilder()
|
||||
.setSigningKey(secretKey)
|
||||
Jwts.parser()
|
||||
.verifyWith(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token);
|
||||
.parseSignedClaims(token);
|
||||
return true;
|
||||
} catch (SecurityException ex) {
|
||||
log.error("Invalid JWT signature: {}", ex.getMessage());
|
||||
@ -115,12 +123,4 @@ public class JwtTokenProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 액세스 토큰 유효시간 반환
|
||||
*
|
||||
* @return 액세스 토큰 유효시간 (밀리초)
|
||||
*/
|
||||
public long getAccessTokenValidityTime() {
|
||||
return accessTokenValidityTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 로그아웃 요청 DTO
|
||||
|
||||
@ -4,26 +4,79 @@ import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstruct
|
||||
재시도
|
||||
Y
|
||||
계속
|
||||
편집
|
||||
Member 서비스 모든 클래스 구현
|
||||
코드 ∙ 버전 2
|
||||
/**
|
||||
* 사용자 ID로 회원 조회
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 회원 정보 (Optional)
|
||||
*/
|
||||
Optional<Member> findByUserId(String userId);
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 회원 엔티티
|
||||
* 회원의 기본 정보와 사업자 정보를 관리
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "members")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class Member {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "member_id")
|
||||
private Long id;
|
||||
|
||||
@Column(name = "user_id", nullable = false, unique = true, length = 50)
|
||||
private String userId;
|
||||
|
||||
@Column(name = "password", nullable = false, length = 100)
|
||||
private String password;
|
||||
|
||||
@Column(name = "name", nullable = false, length = 50)
|
||||
private String name;
|
||||
|
||||
@Column(name = "business_number", length = 12)
|
||||
private String businessNumber;
|
||||
|
||||
@Column(name = "email", nullable = false, unique = true, length = 100)
|
||||
private String email;
|
||||
|
||||
@CreatedDate
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@LastModifiedDate
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 사용자 ID 존재 여부 확인
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 존재 여부
|
||||
|
||||
Member 인증 서비스 구현체 및 Controllers
|
||||
코드
|
||||
* 회원 정보 업데이트
|
||||
*
|
||||
* @param name 이름
|
||||
* @param email 이메일
|
||||
* @param businessNumber 사업자번호
|
||||
*/
|
||||
public void updateProfile(String name, String email, String businessNumber) {
|
||||
if (name != null && !name.trim().isEmpty()) {
|
||||
this.name = name;
|
||||
}
|
||||
if (email != null && !email.trim().isEmpty()) {
|
||||
this.email = email;
|
||||
}
|
||||
if (businessNumber != null && !businessNumber.trim().isEmpty()) {
|
||||
this.businessNumber = businessNumber;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 패스워드 변경
|
||||
*
|
||||
* @param encodedPassword 암호화된 패스워드
|
||||
*/
|
||||
public void changePassword(String encodedPassword) {
|
||||
this.password = encodedPassword;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,21 +2,174 @@ package com.won.smarketing.member.service;
|
||||
|
||||
import com.won.smarketing.common.exception.BusinessException;
|
||||
import com.won.smarketing.common.exception.ErrorCode;
|
||||
import com.
|
||||
재시도
|
||||
Y
|
||||
계속
|
||||
편집
|
||||
Member 인증 서비스 구현체 및 Controllers
|
||||
코드 ∙ 버전 2
|
||||
// 새로운 리프레시 토큰을 Redis에 저장
|
||||
redisTemplate.opsForValue().set(
|
||||
REFRESH_TOKEN_PREFIX + userId,
|
||||
newRefreshToken,
|
||||
7,
|
||||
TimeUnit.DAYS
|
||||
);
|
||||
|
||||
// 기존 리프레시 토큰을
|
||||
Store 서비스 Entity 및 DTO 클래스들
|
||||
코드
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 인증 서비스 구현체
|
||||
* 로그인, 로그아웃, 토큰 갱신 기능 구현
|
||||
*/
|
||||
@Slf4j
|
||||
@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:";
|
||||
private static final String BLACKLIST_PREFIX = "blacklist:";
|
||||
|
||||
/**
|
||||
* 로그인
|
||||
*
|
||||
* @param request 로그인 요청 정보
|
||||
* @return 로그인 응답 정보 (토큰 포함)
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public LoginResponse login(LoginRequest request) {
|
||||
log.info("로그인 시도: {}", request.getUserId());
|
||||
|
||||
// 회원 조회
|
||||
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);
|
||||
}
|
||||
|
||||
// 토큰 생성
|
||||
String accessToken = jwtTokenProvider.generateAccessToken(member.getUserId());
|
||||
String refreshToken = jwtTokenProvider.generateRefreshToken(member.getUserId());
|
||||
|
||||
// 리프레시 토큰을 Redis에 저장 (7일)
|
||||
redisTemplate.opsForValue().set(
|
||||
REFRESH_TOKEN_PREFIX + member.getUserId(),
|
||||
refreshToken,
|
||||
7,
|
||||
TimeUnit.DAYS
|
||||
);
|
||||
|
||||
log.info("로그인 성공: {}", request.getUserId());
|
||||
|
||||
return LoginResponse.builder()
|
||||
.accessToken(accessToken)
|
||||
.refreshToken(refreshToken)
|
||||
.expiresIn(jwtTokenProvider.getAccessTokenValidityTime() / 1000)
|
||||
.userInfo(LoginResponse.UserInfo.builder()
|
||||
.userId(member.getUserId())
|
||||
.name(member.getName())
|
||||
.email(member.getEmail())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃
|
||||
*
|
||||
* @param refreshToken 리프레시 토큰
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public void logout(String refreshToken) {
|
||||
try {
|
||||
if (jwtTokenProvider.validateToken(refreshToken)) {
|
||||
String userId = jwtTokenProvider.getUserIdFromToken(refreshToken);
|
||||
|
||||
// Redis에서 리프레시 토큰 삭제
|
||||
redisTemplate.delete(REFRESH_TOKEN_PREFIX + userId);
|
||||
|
||||
// 리프레시 토큰을 블랙리스트에 추가
|
||||
redisTemplate.opsForValue().set(
|
||||
BLACKLIST_PREFIX + refreshToken,
|
||||
"logout",
|
||||
7,
|
||||
TimeUnit.DAYS
|
||||
);
|
||||
|
||||
log.info("로그아웃 완료: {}", userId);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.warn("로그아웃 처리 중 오류 발생: {}", ex.getMessage());
|
||||
// 로그아웃은 실패해도 클라이언트에게는 성공으로 응답
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰 갱신
|
||||
*
|
||||
* @param refreshToken 리프레시 토큰
|
||||
* @return 새로운 토큰 정보
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public TokenResponse refresh(String refreshToken) {
|
||||
// 토큰 유효성 검증
|
||||
if (!jwtTokenProvider.validateToken(refreshToken)) {
|
||||
throw new BusinessException(ErrorCode.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
// 블랙리스트 확인
|
||||
if (redisTemplate.hasKey(BLACKLIST_PREFIX + refreshToken)) {
|
||||
throw new BusinessException(ErrorCode.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
String userId = jwtTokenProvider.getUserIdFromToken(refreshToken);
|
||||
|
||||
// Redis에 저장된 리프레시 토큰과 비교
|
||||
String storedRefreshToken = redisTemplate.opsForValue().get(REFRESH_TOKEN_PREFIX + userId);
|
||||
if (!refreshToken.equals(storedRefreshToken)) {
|
||||
throw new BusinessException(ErrorCode.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
// 회원 존재 확인
|
||||
if (!memberRepository.existsByUserId(userId)) {
|
||||
throw new BusinessException(ErrorCode.MEMBER_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 새로운 토큰 생성
|
||||
String newAccessToken = jwtTokenProvider.generateAccessToken(userId);
|
||||
String newRefreshToken = jwtTokenProvider.generateRefreshToken(userId);
|
||||
|
||||
// 새로운 리프레시 토큰을 Redis에 저장
|
||||
redisTemplate.opsForValue().set(
|
||||
REFRESH_TOKEN_PREFIX + userId,
|
||||
newRefreshToken,
|
||||
7,
|
||||
TimeUnit.DAYS
|
||||
);
|
||||
|
||||
// 기존 리프레시 토큰을 블랙리스트에 추가
|
||||
redisTemplate.opsForValue().set(
|
||||
BLACKLIST_PREFIX + refreshToken,
|
||||
"refreshed",
|
||||
7,
|
||||
TimeUnit.DAYS
|
||||
);
|
||||
|
||||
log.info("토큰 갱신 완료: {}", userId);
|
||||
|
||||
return TokenResponse.builder()
|
||||
.accessToken(newAccessToken)
|
||||
.refreshToken(newRefreshToken)
|
||||
.expiresIn(jwtTokenProvider.getAccessTokenValidityTime() / 1000)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user