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 날씨 데이터
|
* @return 날씨 데이터
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public WeatherData getCurrentWeather(String location) {
|
public WeatherApiResponse getCurrentWeather(String location) {
|
||||||
try {
|
try {
|
||||||
log.debug("날씨 정보 조회 시작: location={}", location);
|
log.debug("날씨 정보 조회 시작: location={}", location);
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AI 마케팅 추천을 위한 REST API 컨트롤러
|
* AI 마케팅 추천을 위한 REST API 컨트롤러
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.won.smarketing.recommend.presentation.dto;
|
|||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ import java.time.LocalDateTime;
|
|||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@Builder
|
||||||
@Schema(description = "AI 마케팅 팁 생성 응답")
|
@Schema(description = "AI 마케팅 팁 생성 응답")
|
||||||
public class MarketingTipResponse {
|
public class MarketingTipResponse {
|
||||||
|
|
||||||
|
|||||||
@ -18,12 +18,6 @@ subprojects {
|
|||||||
apply plugin: 'org.springframework.boot'
|
apply plugin: 'org.springframework.boot'
|
||||||
apply plugin: 'io.spring.dependency-management'
|
apply plugin: 'io.spring.dependency-management'
|
||||||
|
|
||||||
java {
|
|
||||||
toolchain {
|
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
compileOnly {
|
compileOnly {
|
||||||
extendsFrom annotationProcessor
|
extendsFrom annotationProcessor
|
||||||
@ -32,6 +26,7 @@ subprojects {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
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-data-jpa'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package com.won.smarketing.common.security;
|
|||||||
|
|
||||||
import io.jsonwebtoken.*;
|
import io.jsonwebtoken.*;
|
||||||
import io.jsonwebtoken.security.Keys;
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@ -18,6 +19,13 @@ import java.util.Date;
|
|||||||
public class JwtTokenProvider {
|
public class JwtTokenProvider {
|
||||||
|
|
||||||
private final SecretKey secretKey;
|
private final SecretKey secretKey;
|
||||||
|
/**
|
||||||
|
* -- GETTER --
|
||||||
|
* 액세스 토큰 유효시간 반환
|
||||||
|
*
|
||||||
|
* @return 액세스 토큰 유효시간 (밀리초)
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
private final long accessTokenValidityTime;
|
private final long accessTokenValidityTime;
|
||||||
private final long refreshTokenValidityTime;
|
private final long refreshTokenValidityTime;
|
||||||
|
|
||||||
@ -29,8 +37,8 @@ public class JwtTokenProvider {
|
|||||||
* @param refreshTokenValidityTime 리프레시 토큰 유효시간 (밀리초)
|
* @param refreshTokenValidityTime 리프레시 토큰 유효시간 (밀리초)
|
||||||
*/
|
*/
|
||||||
public JwtTokenProvider(@Value("${jwt.secret}") String secret,
|
public JwtTokenProvider(@Value("${jwt.secret}") String secret,
|
||||||
@Value("${jwt.access-token-validity}") long accessTokenValidityTime,
|
@Value("${jwt.access-token-validity}") long accessTokenValidityTime,
|
||||||
@Value("${jwt.refresh-token-validity}") long refreshTokenValidityTime) {
|
@Value("${jwt.refresh-token-validity}") long refreshTokenValidityTime) {
|
||||||
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
|
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes());
|
||||||
this.accessTokenValidityTime = accessTokenValidityTime;
|
this.accessTokenValidityTime = accessTokenValidityTime;
|
||||||
this.refreshTokenValidityTime = refreshTokenValidityTime;
|
this.refreshTokenValidityTime = refreshTokenValidityTime;
|
||||||
@ -47,9 +55,9 @@ public class JwtTokenProvider {
|
|||||||
Date expiryDate = new Date(now.getTime() + accessTokenValidityTime);
|
Date expiryDate = new Date(now.getTime() + accessTokenValidityTime);
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.setSubject(userId)
|
.subject(userId)
|
||||||
.setIssuedAt(now)
|
.issuedAt(now)
|
||||||
.setExpiration(expiryDate)
|
.expiration(expiryDate)
|
||||||
.signWith(secretKey)
|
.signWith(secretKey)
|
||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
@ -65,9 +73,9 @@ public class JwtTokenProvider {
|
|||||||
Date expiryDate = new Date(now.getTime() + refreshTokenValidityTime);
|
Date expiryDate = new Date(now.getTime() + refreshTokenValidityTime);
|
||||||
|
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.setSubject(userId)
|
.subject(userId)
|
||||||
.setIssuedAt(now)
|
.issuedAt(now)
|
||||||
.setExpiration(expiryDate)
|
.expiration(expiryDate)
|
||||||
.signWith(secretKey)
|
.signWith(secretKey)
|
||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
@ -79,11 +87,11 @@ public class JwtTokenProvider {
|
|||||||
* @return 사용자 ID
|
* @return 사용자 ID
|
||||||
*/
|
*/
|
||||||
public String getUserIdFromToken(String token) {
|
public String getUserIdFromToken(String token) {
|
||||||
Claims claims = Jwts.parserBuilder()
|
Claims claims = Jwts.parser()
|
||||||
.setSigningKey(secretKey)
|
.verifyWith(secretKey)
|
||||||
.build()
|
.build()
|
||||||
.parseClaimsJws(token)
|
.parseSignedClaims(token)
|
||||||
.getBody();
|
.getPayload();
|
||||||
|
|
||||||
return claims.getSubject();
|
return claims.getSubject();
|
||||||
}
|
}
|
||||||
@ -96,10 +104,10 @@ public class JwtTokenProvider {
|
|||||||
*/
|
*/
|
||||||
public boolean validateToken(String token) {
|
public boolean validateToken(String token) {
|
||||||
try {
|
try {
|
||||||
Jwts.parserBuilder()
|
Jwts.parser()
|
||||||
.setSigningKey(secretKey)
|
.verifyWith(secretKey)
|
||||||
.build()
|
.build()
|
||||||
.parseClaimsJws(token);
|
.parseSignedClaims(token);
|
||||||
return true;
|
return true;
|
||||||
} catch (SecurityException ex) {
|
} catch (SecurityException ex) {
|
||||||
log.error("Invalid JWT signature: {}", ex.getMessage());
|
log.error("Invalid JWT signature: {}", ex.getMessage());
|
||||||
@ -115,12 +123,4 @@ public class JwtTokenProvider {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 액세스 토큰 유효시간 반환
|
|
||||||
*
|
|
||||||
* @return 액세스 토큰 유효시간 (밀리초)
|
|
||||||
*/
|
|
||||||
public long getAccessTokenValidityTime() {
|
|
||||||
return accessTokenValidityTime;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -6,7 +6,7 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 로그아웃 요청 DTO
|
* 로그아웃 요청 DTO
|
||||||
|
|||||||
@ -4,26 +4,79 @@ import jakarta.persistence.*;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstruct
|
import lombok.NoArgsConstructor;
|
||||||
재시도
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
Y
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
계속
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
편집
|
|
||||||
Member 서비스 모든 클래스 구현
|
import java.time.LocalDateTime;
|
||||||
코드 ∙ 버전 2
|
|
||||||
/**
|
/**
|
||||||
* 사용자 ID로 회원 조회
|
* 회원 엔티티
|
||||||
*
|
* 회원의 기본 정보와 사업자 정보를 관리
|
||||||
* @param userId 사용자 ID
|
*/
|
||||||
* @return 회원 정보 (Optional)
|
@Entity
|
||||||
*/
|
@Table(name = "members")
|
||||||
Optional<Member> findByUserId(String userId);
|
@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
|
* @param name 이름
|
||||||
* @return 존재 여부
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Member 인증 서비스 구현체 및 Controllers
|
/**
|
||||||
코드
|
* 패스워드 변경
|
||||||
|
*
|
||||||
|
* @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.BusinessException;
|
||||||
import com.won.smarketing.common.exception.ErrorCode;
|
import com.won.smarketing.common.exception.ErrorCode;
|
||||||
import com.
|
import com.won.smarketing.common.security.JwtTokenProvider;
|
||||||
재시도
|
import com.won.smarketing.member.dto.LoginRequest;
|
||||||
Y
|
import com.won.smarketing.member.dto.LoginResponse;
|
||||||
계속
|
import com.won.smarketing.member.dto.TokenResponse;
|
||||||
편집
|
import com.won.smarketing.member.entity.Member;
|
||||||
Member 인증 서비스 구현체 및 Controllers
|
import com.won.smarketing.member.repository.MemberRepository;
|
||||||
코드 ∙ 버전 2
|
import lombok.RequiredArgsConstructor;
|
||||||
// 새로운 리프레시 토큰을 Redis에 저장
|
import lombok.extern.slf4j.Slf4j;
|
||||||
redisTemplate.opsForValue().set(
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
REFRESH_TOKEN_PREFIX + userId,
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
newRefreshToken,
|
import org.springframework.stereotype.Service;
|
||||||
7,
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
TimeUnit.DAYS
|
|
||||||
);
|
|
||||||
|
|
||||||
// 기존 리프레시 토큰을
|
import java.util.concurrent.TimeUnit;
|
||||||
Store 서비스 Entity 및 DTO 클래스들
|
|
||||||
코드
|
/**
|
||||||
|
* 인증 서비스 구현체
|
||||||
|
* 로그인, 로그아웃, 토큰 갱신 기능 구현
|
||||||
|
*/
|
||||||
|
@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