From 585faa462c16939b35dcebf95918b8975be051d0 Mon Sep 17 00:00:00 2001 From: mariasoo <9723maria@gmail.com> Date: Tue, 7 May 2024 12:38:53 +0900 Subject: [PATCH 01/49] login : make jwt using Security --- build.gradle | 7 ++ .../starroad/common/util/KeyGenerator.java | 14 +++ .../config/TokenAuthenticationFilter.java | 97 +++++++++++++++ .../starroad/config/WebSecurityConfig.java | 58 +++++++++ .../starroad/config/jwt/JwtProperties.java | 23 ++++ .../starroad/config/jwt/TokenProvider.java | 100 ++++++++++++++++ .../user/controller/TokenApiController.java | 44 +++++++ .../user/controller/UserController.java | 36 +++++- .../user/dto/CreateAccessTokenRequest.java | 24 ++++ .../user/dto/CreateAccessTokenResponse.java | 17 +++ .../ssg/starroad/user/dto/LoginRequest.java | 13 ++ .../ssg/starroad/user/dto/LoginResponse.java | 20 ++++ .../com/ssg/starroad/user/dto/UserDTO.java | 28 +++++ .../starroad/user/entity/RefreshToken.java | 58 +++++++++ .../com/ssg/starroad/user/entity/User.java | 78 +++++++++++- .../ssg/starroad/user/enums/ActiveStatus.java | 2 + .../repository/RefreshTokenRepository.java | 18 +++ .../user/repository/UserRepository.java | 3 + .../user/service/RefreshTokenService.java | 7 ++ .../starroad/user/service/TokenService.java | 5 + .../starroad/user/service/UserService.java | 12 +- .../service/impl/RefreshTokenServiceImpl.java | 22 ++++ .../user/service/impl/TokenServiceImpl.java | 40 +++++++ .../user/service/impl/UserServiceImpl.java | 63 ++++++++++ .../ssg/starroad/config/jwt/JwtFactory.java | 57 +++++++++ .../config/jwt/TokenProviderTest.java | 113 ++++++++++++++++++ .../controller/TokenApiControllerTest.java | 107 +++++++++++++++++ 27 files changed, 1059 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/ssg/starroad/common/util/KeyGenerator.java create mode 100644 src/main/java/com/ssg/starroad/config/TokenAuthenticationFilter.java create mode 100644 src/main/java/com/ssg/starroad/config/WebSecurityConfig.java create mode 100644 src/main/java/com/ssg/starroad/config/jwt/JwtProperties.java create mode 100644 src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java create mode 100644 src/main/java/com/ssg/starroad/user/controller/TokenApiController.java create mode 100644 src/main/java/com/ssg/starroad/user/dto/CreateAccessTokenRequest.java create mode 100644 src/main/java/com/ssg/starroad/user/dto/CreateAccessTokenResponse.java create mode 100644 src/main/java/com/ssg/starroad/user/dto/LoginRequest.java create mode 100644 src/main/java/com/ssg/starroad/user/dto/LoginResponse.java create mode 100644 src/main/java/com/ssg/starroad/user/dto/UserDTO.java create mode 100644 src/main/java/com/ssg/starroad/user/entity/RefreshToken.java create mode 100644 src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java create mode 100644 src/main/java/com/ssg/starroad/user/service/RefreshTokenService.java create mode 100644 src/main/java/com/ssg/starroad/user/service/TokenService.java create mode 100644 src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java create mode 100644 src/main/java/com/ssg/starroad/user/service/impl/TokenServiceImpl.java create mode 100644 src/test/java/com/ssg/starroad/config/jwt/JwtFactory.java create mode 100644 src/test/java/com/ssg/starroad/config/jwt/TokenProviderTest.java create mode 100644 src/test/java/com/ssg/starroad/user/controller/TokenApiControllerTest.java diff --git a/build.gradle b/build.gradle index 41f0421..dfaf554 100644 --- a/build.gradle +++ b/build.gradle @@ -32,6 +32,8 @@ dependencies { // LOMBOK compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' // SPRING BOOT DEVTOOLS developmentOnly 'org.springframework.boot:spring-boot-devtools' @@ -47,6 +49,11 @@ dependencies { annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // JWT Support + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + // JSON 처리를 위해 jackson 연동 모듈 추가 + // TEST IMPLEMENTATION ======================== // TEST DATABASE H2 testImplementation 'com.h2database:h2' diff --git a/src/main/java/com/ssg/starroad/common/util/KeyGenerator.java b/src/main/java/com/ssg/starroad/common/util/KeyGenerator.java new file mode 100644 index 0000000..5e28b9a --- /dev/null +++ b/src/main/java/com/ssg/starroad/common/util/KeyGenerator.java @@ -0,0 +1,14 @@ +package com.ssg.starroad.common.util; + +import java.security.SecureRandom; +import java.util.Base64; + +public class KeyGenerator { + public static void main(String[] args) { + SecureRandom random = new SecureRandom(); + byte[] bytes = new byte[32]; // 32바이트로 256비트 키 생성 + random.nextBytes(bytes); + String encodedKey = Base64.getEncoder().encodeToString(bytes); + System.out.println("생성된 Base64 인코딩된 비밀 키: " + encodedKey); + } +} diff --git a/src/main/java/com/ssg/starroad/config/TokenAuthenticationFilter.java b/src/main/java/com/ssg/starroad/config/TokenAuthenticationFilter.java new file mode 100644 index 0000000..decddfa --- /dev/null +++ b/src/main/java/com/ssg/starroad/config/TokenAuthenticationFilter.java @@ -0,0 +1,97 @@ +package com.ssg.starroad.config; + +import com.ssg.starroad.config.jwt.TokenProvider; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +// Spring Security의 일부로서 인증 과정에서 중요한 역할 +// HTTP 요청에서 인증 토큰 (예: JWT)을 추출하고, 이를 검증하여 사용자의 인증 상태를 결정하는 것 +// Spring Security에서 제공하는 OncePerRequestFilter 클래스를 상속받아 구현하는 것이 일반적 +// OncePerRequestFilter를 사용하면, 필터가 한 요청에 대해 한 번만 실행되는 것을 보장 +// doFilterInternal 메소드를 오버라이드하여 필터의 로직을 구현 +@RequiredArgsConstructor +public class TokenAuthenticationFilter extends OncePerRequestFilter { + private final TokenProvider tokenProvider; + // 토큰 생성하고 검증하는 컴포넌트 + + private final static String HEADER_AUTHORIZATION = "Authorization"; + // HTTP 요청에서 사용되는 헤더 필드의 이름 정의 + // 인증 토큰은 보통 이 헤더에 "Bearer" 형식으로 포함됨 + // 소유자라는 뜻인데 이 토큰의 소유자에게 권한을 부여해 + + private final static String TOKEN_PREFIX = "Bearer "; + // 인증 헤더에 사용되는 토큰의 접두사 실제 토큰 값만 추출하기 위해 사용 + + private final static Logger log = LoggerFactory.getLogger(TokenAuthenticationFilter.class); + // 로깅 + + // 실제 필터의 로직을 구현하는 메소드 + // 요청의 헤더에서 토큰을 추출하고, 이 토큰이 유효한 경우에만 + // Spring Security의 SecurityContext에 인증 객체(Authentication)를 설정 + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + // 1. HTTP 요청에서 Authorization 헤더는 사용자 인증 정보를 전송하는 데 사용되는 표준 헤더 + // 특히 보안이 필요한 애플리케이션에서 이 헤더를 사용하여 클라이언트가 서버에 자신의 인증 정보를 제공 + // Bearer Token (주로 JWT 사용): 클라이언트는 접근 토큰을 'Bearer' 라는 접두사와 함께 전송 + String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION); + String token = getAccessToken(authorizationHeader); + + // 2. TokenProvider를 사용하여 토큰 검증 + if (token != null) { + try { + // 3. 유효한 토큰인 경우, 사용자 인증 객체를 생성하고 SecurityContextHolder에 등록 + if (tokenProvider.validToken(token)) { + Authentication authentication = tokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + log.info("JWT가 유효하지 않음: {}", token); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + // 4. 유효하지 않은 토큰 또는 예외 발생 시 적절한 HTTP 상태 코드 설정 + } catch (Exception e) { + log.error("Authentication error: ", e); + response.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + } + + filterChain.doFilter(request, response); + } + + // Authorization 헤더에서 실제 토큰 값을 추출 + // TOKEN_PREFIX는 "Bearer "로 설정되어 있으며, 이는 토큰 타입을 명시하는 표준 방식 + // substring 메소드를 사용하여 "Bearer " 다음의 문자열을 잘라내고, 실제 토큰 값을 반환 + // 이렇게 추출된 토큰은 유효성 검사를 위해 TokenProvider에 전달 + // 유효한 경우 사용자 인증 정보를 생성하여 SecurityContext에 설정 + private String getAccessToken(String authorizationHeader) { + if (authorizationHeader != null && authorizationHeader.startsWith(TOKEN_PREFIX)) { + return authorizationHeader.substring(TOKEN_PREFIX.length()); + } + return null; + } + + // 특정 경로에 대해 필터를 적용하지 않도록 설정 + // 일반적으로 인증이 필요 없는 공개 API 경로에서 필터를 적용하지 않도록 하여 성능을 최적화 + // 로그인, 회원 가입, 공개적으로 접근 가능한 정보 제공 API 등 인증이 필요 없는 경로들에 대해 필터를 적용하지 않도록 설정 + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String path = request.getRequestURI(); + return path.startsWith("/user/login") || + path.startsWith("/user/join"); + } +} \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java new file mode 100644 index 0000000..60d9359 --- /dev/null +++ b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java @@ -0,0 +1,58 @@ +package com.ssg.starroad.config; + +import com.ssg.starroad.user.service.UserService; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig { + + private final UserService userService; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + + public WebSecurityConfig(UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder) { + this.userService = userService; + this.bCryptPasswordEncoder = bCryptPasswordEncoder; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) // CSRF 비활성화!!! + // API는 일반적으로 토큰 기반 인증, 예를 들어 JWT (JSON Web Tokens) 같은 시스템을 사용 + // 이러한 토큰 기반 인증은 사용자의 세션 상태를 서버에 저장하지 않기 때문에 "무상태(stateless)"로 간주 + // 사용자가 직접 요청에 토큰을 첨부해야 하므로, 악의적인 사이트가 사용자를 속여 토큰을 사용하는 요청을 생성하기 어려움 + // 이는 CSRF 공격을 자동으로 방지하는 효과를 가짐 + .authorizeRequests(auth -> auth + .requestMatchers("/user/login", "/user/join").permitAll() + .anyRequest().authenticated() + ) + .formLogin(form -> form + .loginPage("/user/login") + .defaultSuccessUrl("/main", true) + .permitAll() + ) + .logout(logout -> logout + .logoutUrl("/user/logout") // 로그아웃 경로 설정 + .logoutSuccessUrl("/user/login") + .invalidateHttpSession(true) // 세션 무효화 + .deleteCookies("JSESSIONID") // 쿠키 삭제 + // JSESSIONID : 세션 식별자(서버가 사용자의 세션을 관리하는 데 사용) + // 로그아웃 시 사용자의 브라우저에서 서버에 대해 인증 없이 재접속하는 것을 방지하기 위함 + .permitAll() + ); + + return http.build(); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/config/jwt/JwtProperties.java b/src/main/java/com/ssg/starroad/config/jwt/JwtProperties.java new file mode 100644 index 0000000..9156084 --- /dev/null +++ b/src/main/java/com/ssg/starroad/config/jwt/JwtProperties.java @@ -0,0 +1,23 @@ +package com.ssg.starroad.config.jwt; + + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +@Getter +@Setter +@Component +@Validated +@ConfigurationProperties(prefix = "jwt") +// 자바 클래스에 프로퍼티 값을 가져와서 사용. 프로퍼티들을 자동으로 바인딩. +// prefix = "jwt"는 프로퍼티 이름이 jwt.xxx 형식임을 의미 + +public class JwtProperties { + @NotBlank + private String secretKey; + private String issuer; +} diff --git a/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java new file mode 100644 index 0000000..7e9dfe9 --- /dev/null +++ b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java @@ -0,0 +1,100 @@ +package com.ssg.starroad.config.jwt; +// JWT를 생성하고 파싱하여 유효성을 검사하는 역할 +// JwtProperties 클래스에서 설정된 값을 사용하여 토큰을 관리 + +import com.ssg.starroad.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.io.Decoders; +import javax.crypto.SecretKey; +import java.time.Duration; +import java.util.Collections; +import java.util.Date; +import java.util.Set; + +@RequiredArgsConstructor +@Service +public class TokenProvider { + + private final JwtProperties jwtProperties; + + public String generateToken(User user, Duration expiredAt) { + Date now = new Date(); + return createToken(new Date(now.getTime() + expiredAt.toMillis()), user); + } + // 사용자 객체와 만료 시간(Duration)을 받아 jwt 토큰 생성 + // 파라미터 (사용자 정보, 토큰 유효 기간) + // 현재 시간을 구하고 expiredAt으로 전달된 기간을 현재 시간에 더해 토큰 만료 시간expiry 계산 + // creatToken 메소드 호출 실제 토큰 생성 + + + // 실제 jwt 토큰 생성 사용자 정보와 만료 시간을 받아 구성, 서명해 반환 + public String createToken(Date expiry, User user) { + Date now = new Date(); + SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtProperties.getSecretKey())); + + + return Jwts.builder() + .setHeaderParam("typ", "JWT") // 헤더 타입 jwt + // 내용 jwt의 클레임 설정 + .setIssuer(jwtProperties.getIssuer()) // 토큰 발급자 + .setIssuedAt(now) // 토큰 발급 시간 + .setExpiration(expiry) // 토큰 만료 시간 + .setSubject(user.getEmail()) // 사용자 이메일 + .claim("id", user.getId()) // 추가 정보 클레임 id : 유저 아이디 + // 토큰에 사용자 고유 id 정보 담아 토큰 해석 시 사용자 정보 접근에 용이 + // 서명 비밀값과 합께 해시값을 HS256 방식으로 암호화 + .signWith(key) + .compact(); + } + + // 제공된 토큰이 유효한지 검사. 토큰의 서명 검증. 파싱 중 발생할 수 있는 예외 처리 + // 예외가 발생하면 false 그렇지 않으면 true + public boolean validToken(String token) { + try { + SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtProperties.getSecretKey())); + Jwts.parserBuilder() + .setSigningKey(key)// 비밀값으로 복호화 + .build() + .parseClaimsJws(token); + return true; + } catch (Exception e) { // 복호화 과정에서 에러가 나면 유효하지 않은 토큰 + return false; + } + } + + + // 토큰 기반으로 Authentication 인증 객체 생성. 시큐리티에서 사용자의 인증 정보 나타내는 데 사용 + public Authentication getAuthentication(String token) { + Claims claims = getClaims(token); + // 토큰에서 클레임 추출 + // 클레임에서 사용자 역할 포함하는 authorities 설정 + Set authorities = Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")); + + return new UsernamePasswordAuthenticationToken(new org.springframework.security.core.userdetails.User + (claims.getSubject(), "", authorities), token, authorities); + } + + // 토큰에서 사용자 id 추출 + public Long getUserId(String token) { + Claims claims = getClaims(token); + return claims.get("id", Long.class); + } + + // 토큰에서 클레임을 추출하는 보조 메소드 + // 토큰의 파싱 담당 클레임 세트 반환 + private Claims getClaims(String token) { + SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtProperties.getSecretKey())); + return Jwts.parserBuilder() // 클레임 조회 + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/src/main/java/com/ssg/starroad/user/controller/TokenApiController.java b/src/main/java/com/ssg/starroad/user/controller/TokenApiController.java new file mode 100644 index 0000000..8277cda --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/controller/TokenApiController.java @@ -0,0 +1,44 @@ +package com.ssg.starroad.user.controller; + +import com.ssg.starroad.user.dto.CreateAccessTokenRequest; +import com.ssg.starroad.user.dto.CreateAccessTokenResponse; +import com.ssg.starroad.user.service.TokenService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +// 클래스가 RESTful 웹 서비스의 컨트롤러로 작동함 +// 모든 메서드는 HTTP 응답 본문에 직접 작성되며, 객체는 자동으로 JSON 또는 XML로 직렬화 +@RequestMapping("/api/token") +public class TokenApiController { + + private final TokenService tokenService; + + public TokenApiController(TokenService tokenService) { + this.tokenService = tokenService; + } + + // 클라이언트는 리프레시 토큰을 포함한 CreateAccessTokenRequest 객체를 JSON 형태로 서버에 전송 + + // 새 액세스 토큰을 생성하는 요청을 담당 + @PostMapping("/access-token") + // CreateAccessTokenRequest 객체를 요청 본문에서 받아 처리하고, + // CreateAccessTokenResponse 객체를 반환 + // @RequestBody 어노테이션은 HTTP 요청 본문을 자바 객체로 변환하는 데 사용 + public ResponseEntity createAccessToken(@RequestBody CreateAccessTokenRequest request) { + String newAccessToken = tokenService.createNewAccessToken(request.getRefreshToken()); + // 서버는 요청을 받고 TokenService를 사용하여 리프레시 토큰의 유효성을 검증 + // 유효한 토큰인 경우, 관련된 사용자 정보를 기반으로 새 액세스 토큰을 생성 + + return ResponseEntity.ok(new CreateAccessTokenResponse(newAccessToken)); + // 생성된 액세스 토큰을 클라이언트에게 반환하는 데 사용 + // ok() 메서드는 HTTP 200 상태 코드를 설정 + // 응답 본문에는 생성된 토큰이 포함 + + // 클라이언트는 이 토큰을 사용하여 권한이 필요한 리소스에 접근 가능 + } + +} diff --git a/src/main/java/com/ssg/starroad/user/controller/UserController.java b/src/main/java/com/ssg/starroad/user/controller/UserController.java index ba73e55..a1fcb34 100644 --- a/src/main/java/com/ssg/starroad/user/controller/UserController.java +++ b/src/main/java/com/ssg/starroad/user/controller/UserController.java @@ -1,14 +1,46 @@ package com.ssg.starroad.user.controller; +import com.ssg.starroad.config.jwt.TokenProvider; +import com.ssg.starroad.user.dto.LoginRequest; +import com.ssg.starroad.user.dto.LoginResponse; +import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.service.UserService; -import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.Date; + @RequestMapping("/user") @RestController -@RequiredArgsConstructor public class UserController { private final UserService userService; + private final TokenProvider tokenProvider; + + public UserController(UserService userService, TokenProvider tokenProvider) { + this.userService = userService; + this.tokenProvider = tokenProvider; + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest request) { + boolean isValidUser = userService.validateUser(request.getEmail(), request.getPassword()); + if (isValidUser) { + UserDetails userDetails = userService.loadUserByUsername(request.getEmail()); + User user = (User) userDetails; // UserDetails를 User로 캐스팅 + + // 토큰 만료 시간 설정: 현재 시간으로부터 10분 후 + Date expiry = new Date(System.currentTimeMillis() + 600 * 1000); // 600 seconds (10 minutes) + + String accessToken = tokenProvider.createToken(expiry, user); // 만료 시간을 인자로 추가 + String refreshToken = refreshTokenService.createRefreshToken(user.getId()); // 리프레시 토큰 생성 로직 + return ResponseEntity.ok(new LoginResponse(accessToken, refreshToken)); + } + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } } diff --git a/src/main/java/com/ssg/starroad/user/dto/CreateAccessTokenRequest.java b/src/main/java/com/ssg/starroad/user/dto/CreateAccessTokenRequest.java new file mode 100644 index 0000000..b40dbb0 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/dto/CreateAccessTokenRequest.java @@ -0,0 +1,24 @@ +package com.ssg.starroad.user.dto; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor // 모든 필드를 매개변수로 받는 생성자를 추가 +@NoArgsConstructor +// 매개변수 없는 기본 생성자 생성 +// 데이터 바인딩 사용할 때 객체를 생성한 후 세터 메소드를 통해 +// 값을 주입할 수 있기 때문에 기본 생성자가 필요 +// 예를 들어, json 형태의 http 요청 바디를 객체로 변환할 때 사용 + +// 클라이언트로부터 리프레시 토큰을 받아 새로운 액세스 토큰을 생성하기 위한 +// 요청 정보를 담는 역할! 여기엔 보통 리프레스 토큰이 포함되어 있음 +// 사용자로부터 리프레시 토큰을 입력받는 용도! +public class CreateAccessTokenRequest { + private String refreshToken; // 클라이언트가 제공하는 리프레시 토큰 +} + diff --git a/src/main/java/com/ssg/starroad/user/dto/CreateAccessTokenResponse.java b/src/main/java/com/ssg/starroad/user/dto/CreateAccessTokenResponse.java new file mode 100644 index 0000000..7d967f8 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/dto/CreateAccessTokenResponse.java @@ -0,0 +1,17 @@ +package com.ssg.starroad.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +// 모든 필드를 매개변수로 받는 생성자 생성 +// 모든 필드 값을 초기화해야 할 필요가 있음 +// 생성 시점에 모든 데이터를 받아 객체 상태를 완전히 정의해야 하는 경우 사용 +// 생성된 액세스 토큰을 필수적으로 포함해 인스턴스를 생성해야 해서 모든 인자를 받는 생성자가 필요함! + +// 새로 생성된 액세스 토큰을 클라이언트에게 반환할 때 사용 +// 클라이언트에게 액세스 토큰 정보를 제공하기 위한 목적! +public class CreateAccessTokenResponse { + private String accessToken; // 생성된 액세스 토큰 +} \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/dto/LoginRequest.java b/src/main/java/com/ssg/starroad/user/dto/LoginRequest.java new file mode 100644 index 0000000..c5da68d --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/dto/LoginRequest.java @@ -0,0 +1,13 @@ +package com.ssg.starroad.user.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class LoginRequest { + private String email; + private String password; +} diff --git a/src/main/java/com/ssg/starroad/user/dto/LoginResponse.java b/src/main/java/com/ssg/starroad/user/dto/LoginResponse.java new file mode 100644 index 0000000..2dcc876 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/dto/LoginResponse.java @@ -0,0 +1,20 @@ +package com.ssg.starroad.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class LoginResponse { + private String accessToken; + private String refreshToken; +} + + +// 응답에서 리프레시 토큰을 포함하는 이유 --- +// 사용자가 로그인할 때 마다 새로운 리프레시 토큰을 발급하는 것은, +// 토큰의 보안을 강화하고, 오래된 또는 잠재적으로 노출된 리프레시 토큰을 무효화하는 효과적인 방법 +// 일부 시스템에서는 리프레시 토큰의 재사용을 방지하기 위해 매 로그인 시 또는 매 토큰 갱신 시 +// 새로운 리프레시 토큰을 발급하고 이전 토큰을 무효화 +// 따라서, 로그인 응답에 리프레시 토큰을 포함하는 것은 +// 토큰 기반 인증 시스템에서 흔히 볼 수 있는 보안 관행이며, 이를 통해 사용자의 로그인 상태를 안전하게 유지 가능 \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/dto/UserDTO.java b/src/main/java/com/ssg/starroad/user/dto/UserDTO.java new file mode 100644 index 0000000..e9dcd0e --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/dto/UserDTO.java @@ -0,0 +1,28 @@ +package com.ssg.starroad.user.dto; + +import com.ssg.starroad.user.enums.ActiveStatus; +import com.ssg.starroad.user.enums.Gender; +import lombok.*; + +import java.time.LocalDate; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class UserDTO { + private Long id; + private String password; + private String name; + private String nickname; + private String email; + private Gender gender; + private LocalDate birth; + private String phone; + private String imagePath; + private String provider; + private int reviewExp; + private int point; + private ActiveStatus activeStatus; +} diff --git a/src/main/java/com/ssg/starroad/user/entity/RefreshToken.java b/src/main/java/com/ssg/starroad/user/entity/RefreshToken.java new file mode 100644 index 0000000..ede1fea --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/entity/RefreshToken.java @@ -0,0 +1,58 @@ +package com.ssg.starroad.user.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.Instant; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity + +// 1. 장기 인증 지원: 리프레시 토큰은 일반적으로 액세스 토큰보다 훨씬 긴 수명을 가짐. +// 그래서 사용자는 자주 로그인할 필요 없이 지속적으로 서비스를 이용 가능. +// 2. 보안성 향상: 액세스 토큰은 짧은 수명을 가지므로, 만약 이 토큰이 탈취당하더라도 사용할 수 있는 시간이 제한적. +// 리프레시 토큰을 이용해 새 액세스 토큰을 발급받는 방식은 액세스 토큰이 노출될 위험을 줄여줍니다. +public class RefreshToken { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + // RefreshToken 인스턴스를 유일하게 식별하는 데 사용 + // 자동 생성된 ID는 토큰 관리를 더욱 효율적으로 + + @Column(nullable = false, unique = true) + private String token; + // 리프레시 토큰은 보안 상 중요한 정보 + // 각 토큰 값은 유일무이, 사용자 인증이나 세션 관리 시 안전하게 사용 가능 + + @Column(nullable = false) + private Instant expiryDate; + // 만료 시간은 토큰의 유효성을 검증할 때 중요한 역할 + // 만료된 토큰은 자동으로 무효화 + // 세계 표준시(UTC)를 기준 + // 시간의 덧셈, 뺄셈과 같은 시간 연산을 수행하는 다양한 메소드를 제공 + // 예 : Instant.now().plusSeconds(3600)은 현재 시간으로부터 1시간 후 + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", referencedColumnName = "id") + private User user; + // RefreshToken과 User 엔티티 간의 일대일 관계를 설정 + // 토큰이 특정 사용자에게 속함을 명확히 + // 지연 로딩(FetchType.LAZY)을 사용해 성능을 최적화 + // User 엔티티는 실제로 접근할 때만 데이터베이스에서 로드 + + + public RefreshToken(User user, String token, Instant expiryDate) { + this.user = user; + this.token = token; + this.expiryDate = expiryDate; + } + public void updateToken(String newToken) { + this.token = newToken; + } + + // 새로운 리프레시 토큰이 발행될 때 사용 +} diff --git a/src/main/java/com/ssg/starroad/user/entity/User.java b/src/main/java/com/ssg/starroad/user/entity/User.java index b467049..c73cc77 100644 --- a/src/main/java/com/ssg/starroad/user/entity/User.java +++ b/src/main/java/com/ssg/starroad/user/entity/User.java @@ -4,37 +4,67 @@ import com.ssg.starroad.user.enums.ActiveStatus; import com.ssg.starroad.user.enums.Gender; import jakarta.persistence.*; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Past; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import java.time.LocalDate; +import java.util.Collection; +import java.util.Collections; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; @Getter +@Builder @Entity @NoArgsConstructor(access = PROTECTED) @AllArgsConstructor -public class User extends BaseTimeEntity { +public class User extends BaseTimeEntity implements UserDetails { @Id @GeneratedValue(strategy = IDENTITY) + @Column(updatable = false) + // 사용자가 자신의 정보를 업데이트하는 동안, + // 데이터베이스에서 식별자로 사용되는 id 값의 안정성을 보장 private Long id; + @Size(min = 8, max = 15, message = "비밀번호는 8자 이상 15자 이하여야 합니다.") + @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$") private String password; // bcrypt + + @Size(min = 2, max = 10, message = "이름은 2자 이상 10자 이하여야 합니다.") private String name; - @Column(unique = true) + + @Pattern(regexp = "^[가-힣a-zA-Z0-9\\p{Punct}]{3,10}$", + message = "닉네임은 3자 이상 10자 이하로, 영어, 한국어, 숫자, 특수문자를 포함해야 합니다.") + @Column(nullable = false, unique = true) private String nickname; + private String imagePath; // profile image + @Enumerated(EnumType.STRING) private Gender gender; + @Column + @Past(message = "생년월일은 과거 날짜여야 합니다.") private LocalDate birth; - private String phone; // xxx-xxxx-xxxx - @Column(unique = true) + + @Pattern(regexp = "^[0-9]{11}$", message = "전화번호는 하이픈 없이 11자리여야 합니다.") + private String phone; // 하이픈 없이 11자리 + + @Column(nullable = false, unique = true) + @Email(message = "유효한 이메일 주소여야 합니다.") private String email; + @Column(unique = true) private String provider; // provider + providerId => unique @@ -43,4 +73,44 @@ public class User extends BaseTimeEntity { @Enumerated(EnumType.STRING) private ActiveStatus activeStatus; + + + @Override + public Collection getAuthorities() { + // 모든 사용자에게 'ROLE_USER' 권한 부여 + return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + } + + @Override + public String getUsername() { + // 사용자의 아이디 반환 (고유한 값) + return email; + } + + @Override + public boolean isAccountNonExpired() { + // 계정 만료 여부 반환 + return true; + // true 만료되지 않았음 + } + + @Override + public boolean isAccountNonLocked() { + // 계정 잠금 여부 반환 + return true; + // true 잠금되지 않았음 + } + + @Override + public boolean isCredentialsNonExpired() { + // 패스워드 만료 여부 반환 + return true; + // true 만료되지 않았음 + } + + @Override + public boolean isEnabled() { + // activeStatus가 ACTIVE인 경우에만 true 반환 + return this.activeStatus == ActiveStatus.ACTIVE; + } } diff --git a/src/main/java/com/ssg/starroad/user/enums/ActiveStatus.java b/src/main/java/com/ssg/starroad/user/enums/ActiveStatus.java index 59aa212..27e7dd8 100644 --- a/src/main/java/com/ssg/starroad/user/enums/ActiveStatus.java +++ b/src/main/java/com/ssg/starroad/user/enums/ActiveStatus.java @@ -8,7 +8,9 @@ public enum ActiveStatus { INACTIVE("비활성화"), SUSPENDED("정지"), CLOSED("탈퇴"), + // 관리자에 의해 강제로 계정을 닫은 경우 WITHDRAW("탈퇴"); + // 유저가 자발적으로 탈퇴한 경우 private String desc; diff --git a/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java b/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..a822640 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java @@ -0,0 +1,18 @@ +package com.ssg.starroad.user.repository; + +import com.ssg.starroad.user.entity.RefreshToken; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + + +public interface RefreshTokenRepository extends JpaRepository { + Optional findByUser_Id(Long userId); + Optional findByToken(String refreshToken); +} + +// findByUser_Id(Long userId) 메서드를 사용하는 것은 +// 일대일 관계를 통해 특정 사용자의 리프레시 토큰을 쉽게 찾기 위함 +// JPA에서는 연관된 엔티티의 필드를 조회하는 방법으로 +// _ (언더스코어)를 사용하여 엔티티의 필드 경로 지정 가능 +// User 엔티티의 id 값을 이용하여 해당 User의 RefreshToken 조회 가능 \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/repository/UserRepository.java b/src/main/java/com/ssg/starroad/user/repository/UserRepository.java index 659bdf1..2fa4ddb 100644 --- a/src/main/java/com/ssg/starroad/user/repository/UserRepository.java +++ b/src/main/java/com/ssg/starroad/user/repository/UserRepository.java @@ -3,6 +3,9 @@ import com.ssg.starroad.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); } diff --git a/src/main/java/com/ssg/starroad/user/service/RefreshTokenService.java b/src/main/java/com/ssg/starroad/user/service/RefreshTokenService.java new file mode 100644 index 0000000..297e98e --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/RefreshTokenService.java @@ -0,0 +1,7 @@ +package com.ssg.starroad.user.service; + +import com.ssg.starroad.user.entity.RefreshToken; + +public interface RefreshTokenService { + RefreshToken findByRefreshToken(String refreshToken); +} diff --git a/src/main/java/com/ssg/starroad/user/service/TokenService.java b/src/main/java/com/ssg/starroad/user/service/TokenService.java new file mode 100644 index 0000000..aebf71c --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/TokenService.java @@ -0,0 +1,5 @@ +package com.ssg.starroad.user.service; + +public interface TokenService { + String createNewAccessToken(String refreshToken); +} diff --git a/src/main/java/com/ssg/starroad/user/service/UserService.java b/src/main/java/com/ssg/starroad/user/service/UserService.java index 4ccc410..0ee191a 100644 --- a/src/main/java/com/ssg/starroad/user/service/UserService.java +++ b/src/main/java/com/ssg/starroad/user/service/UserService.java @@ -1,4 +1,14 @@ package com.ssg.starroad.user.service; -public interface UserService { +import com.ssg.starroad.user.dto.UserDTO; +import com.ssg.starroad.user.entity.User; +import org.springframework.security.core.userdetails.UserDetailsService; + +public interface UserService extends UserDetailsService { + + User findById(Long userId); + + Long save(UserDTO userDTO); + + boolean validateUser(String email, String password); } diff --git a/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java new file mode 100644 index 0000000..ed5b862 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java @@ -0,0 +1,22 @@ +package com.ssg.starroad.user.service.impl; + +import com.ssg.starroad.user.entity.RefreshToken; +import com.ssg.starroad.user.repository.RefreshTokenRepository; +import com.ssg.starroad.user.service.RefreshTokenService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@RequiredArgsConstructor +@Service +public class RefreshTokenServiceImpl implements RefreshTokenService { + + private final RefreshTokenRepository refreshTokenRepository; + + @Override + public RefreshToken findByRefreshToken(String refreshToken) { + return refreshTokenRepository.findByToken(refreshToken) + .orElseThrow(() -> new IllegalArgumentException("Unexpected token")); + } +} + +// RefreshToken에서 직접적으로 userId를 접근할 필요 없이 관련된 User 객체를 통해 필요한 정보 얻을 수 있음 diff --git a/src/main/java/com/ssg/starroad/user/service/impl/TokenServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/TokenServiceImpl.java new file mode 100644 index 0000000..176f487 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/impl/TokenServiceImpl.java @@ -0,0 +1,40 @@ +package com.ssg.starroad.user.service.impl; + +import com.ssg.starroad.config.jwt.TokenProvider; +import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.service.RefreshTokenService; +import com.ssg.starroad.user.service.TokenService; +import com.ssg.starroad.user.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +@RequiredArgsConstructor +@Service +public class TokenServiceImpl implements TokenService { + private final TokenProvider tokenProvider; + private final RefreshTokenService refreshTokenService; + private final UserService userService; + +// RefreshToken 엔티티는 User 엔티티와 일대일 관계 +// user 필드를 통해 User 엔티티를 참조 +// userId를 직접 가져오는 대신, User 객체를 통해 ID를 가져와야 + @Override + public String createNewAccessToken(String refreshToken) { + + // 토큰 유효성 검사에 실패하면 예외 발생 + if(!tokenProvider.validToken(refreshToken)){ + throw new IllegalArgumentException("Unexpected token"); + } + + // RefreshToken 객체를 찾고, 연결된 User 객체의 ID를 가져오기 + User user = refreshTokenService.findByRefreshToken(refreshToken).getUser(); + // User ID로 User 객체를 조회하지 않고, 바로 User 객체를 사용하기 + return tokenProvider.generateToken(user, Duration.ofHours(2)); + } +} + +// 전달받은 리프레시 토큰으로 토큰 유효성 검사를 진행 +// 유효한 토큰일 때 리프레스 토큰으로 사용자 id를 찾음 +// 토큰 제공자의 generateToken 메서드 호출해 새로운 액세스 토큰 생성 \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index 034a890..4fc0018 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -1,10 +1,73 @@ package com.ssg.starroad.user.service.impl; +import com.ssg.starroad.user.dto.UserDTO; +import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.repository.UserRepository; import com.ssg.starroad.user.service.UserService; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.Collections; +import java.util.Optional; + @Service @RequiredArgsConstructor public class UserServiceImpl implements UserService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + Optional userOptional = userRepository.findByEmail(email); + User user = userOptional.orElseThrow(() -> + new UsernameNotFoundException("해당 이메일을 가진 사용자를 찾을 수 없습니다: " + email)); + + return new org.springframework.security.core.userdetails.User( + user.getEmail(), + user.getPassword(), + Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); // 권한 설정 + } + // Collections.singletonList 단일 항목을 포함하는 변경 불가능한 리스트 생성. 단일 권한 처리 + + // 리프레시 토큰을 이용해 새로운 액세스 토큰을 생성할 때 해당 사용자의 정보를 db에서 조회하려고 + // User 클래스에서 id 필드에 해당하며, @Id 어노테이션으로 지정 + @Override + public User findById(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("Unexpected user")); + } + + + @Override + public Long save(UserDTO userDTO) { + User user = User.builder() + .email(userDTO.getEmail()) + .password(passwordEncoder.encode(userDTO.getPassword())) + .name(userDTO.getName()) + .nickname(userDTO.getNickname()) + .imagePath(userDTO.getImagePath()) + .gender(userDTO.getGender()) + .birth(userDTO.getBirth()) + .phone(userDTO.getPhone()) + .provider(userDTO.getProvider()) + .reviewExp(userDTO.getReviewExp()) + .point(userDTO.getPoint()) + .activeStatus(userDTO.getActiveStatus()) + .build(); + return userRepository.save(user).getId(); + } + public boolean validateUser(String email, String password) { + Optional userOptional = userRepository.findByEmail(email); + if (userOptional.isPresent()) { + User user = userOptional.get(); + return passwordEncoder.matches(password, user.getPassword()) && user.isEnabled(); + } + return false; + } + } diff --git a/src/test/java/com/ssg/starroad/config/jwt/JwtFactory.java b/src/test/java/com/ssg/starroad/config/jwt/JwtFactory.java new file mode 100644 index 0000000..9f843c9 --- /dev/null +++ b/src/test/java/com/ssg/starroad/config/jwt/JwtFactory.java @@ -0,0 +1,57 @@ +package com.ssg.starroad.config.jwt; + +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.Builder; +import lombok.Getter; + +import java.time.Duration; +import java.util.Date; +import java.util.Map; + +import static java.util.Collections.emptyMap; + +// 테스트 실행용 Mocking(모킹) 객체 => 가짜 객체 +@Getter +public class JwtFactory { + + private String subject = "user@example.com"; + private Date issuedAt = new Date(); + private Date expiration = new Date(new Date().getTime() + Duration.ofDays(14).toMillis()); + private Map claims = emptyMap(); + // 비어 있는 맵 반환 클레임 포함하지 않는 기본 설정으로 토큰 생성할 때 사용됨 + // 테스트 시나리오에서 클레임 추가하기 전 기본 상태 제공 + + @Builder + // 빌더 패턴을 사용해 인스턴스 초기화 + // 파라미터가 null이 아닌 경우에만 해당 파라미터로 필드를 초기화하고 + // 그렇지 않으면 기본값을 사용 + public JwtFactory(String subject, Date issuedAt, Date expiration, Map claims) { + this.subject = subject != null ? subject : this.subject; + this.issuedAt = issuedAt != null ? issuedAt : this.issuedAt; + this.expiration = expiration != null ? expiration : this.expiration; + this.claims = claims != null ? claims : this.claims; + } + + // 기본 생성자를 통해 객체 생성 + // 추가 설정 없이 기본값으로 토큰 생성 + public static JwtFactory withDefaultValues() { + return JwtFactory.builder().build(); + } + + // jwt 토큰 생성 메소드 + // jwt 표준 필드와 함께 사용자 정의 클레임을 토큰에 추가 + public String createToken(JwtProperties jwtProperties) { + return Jwts.builder() + .setSubject(subject) + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setIssuer(jwtProperties.getIssuer()) + .setIssuedAt(issuedAt) + .setExpiration(expiration) + .addClaims(claims) // 수정된 부분: 다양한 사용자 정의 클레임을 포함할 수 있도록 수정 + .signWith(SignatureAlgorithm.HS256, jwtProperties.getSecretKey()) + .compact(); + } +} + diff --git a/src/test/java/com/ssg/starroad/config/jwt/TokenProviderTest.java b/src/test/java/com/ssg/starroad/config/jwt/TokenProviderTest.java new file mode 100644 index 0000000..3e1601c --- /dev/null +++ b/src/test/java/com/ssg/starroad/config/jwt/TokenProviderTest.java @@ -0,0 +1,113 @@ +package com.ssg.starroad.config.jwt; + + +import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.repository.UserRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Duration; +import java.util.Date; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +class TokenProviderTest { + + @Autowired + private TokenProvider tokenProvider; + + @Autowired + private UserRepository userRepository; + + @Autowired + private JwtProperties jwtProperties; + + @DisplayName("generateToken(): 유저 정보와 만료 기간을 전달해 토큰을 만들 수 있다.") + @Test + void generateToken() { + // given : 테스트를 위한 사용자 정보를 설정하고 데이터베이스에 저장 + User testUser = userRepository.save(User.builder() + .email("user@example.com") + .password("Valid123$") + .build()); + + // when : 토큰 생성 메서드를 호출하여 JWT 토큰을 생성 + String token = tokenProvider.generateToken(testUser, Duration.ofDays(14)); + + // then : 생성된 토큰에서 사용자 ID를 추출하여 저장된 사용자의 ID와 비교 + // 토큰 복호화. 토큰 만들 때 클레임으로 넣어둔 id 값이 given 절에서 만든 사용자 id랑 동일한지 확인 + Long userId = tokenProvider.getUserId(token); + assertThat(userId).isEqualTo(testUser.getId()); + } + + @DisplayName("validToken(): 만료된 토큰인 경우에 유효성 검증에 실패한다.") + @Test + void validToken_invalidToken() { + // given : 과거 날짜로 설정된 토큰의 만료 시간을 설정 + Date pastDate = new Date(new Date().getTime() - 86400000); // 하루 지났음 + JwtFactory jwtFactory = JwtFactory.builder() + .expiration(pastDate) + .build(); + String token = jwtFactory.createToken(jwtProperties); + + // when : 토큰의 유효성 검증을 수행 + boolean result = tokenProvider.validToken(token); + + // then : 토큰이 유효하지 않으므로 결과는 false + assertThat(result).isFalse(); + } + + + @DisplayName("validToken(): 유효한 토큰인 경우에 유효성 검증에 성공한다.") + @Test + void validToken_validToken() { + // given : 유효성 검증에 사용할 기본 설정의 토큰 생성 + JwtFactory jwtFactory = JwtFactory.withDefaultValues(); + String token = jwtFactory.createToken(jwtProperties); + + // when : 토큰의 유효성 검증을 수행 + boolean result = tokenProvider.validToken(token); + + // then : 토큰이 유효하므로 결과는 true + assertThat(result).isTrue(); + } + + + @DisplayName("getAuthentication(): 토큰 기반으로 인증정보를 가져올 수 있다.") + @Test + void getAuthentication() { + // given : 인증 정보 생성을 위해 사용자를 저장하고 토큰을 생성 + User testUser = userRepository.save(User.builder() + .email("user@example.com") + .password("Valid123$") + .build()); + + // when : 생성된 토큰을 사용하여 인증 정보를 얻음 + String token = JwtFactory.withDefaultValues().createToken(jwtProperties); + Authentication authentication = tokenProvider.getAuthentication(token); + + // then : 인증 정보에서 얻은 사용자 이름이 저장된 사용자의 이메일과 일치하는지 확인 + assertThat(((UserDetails) authentication.getPrincipal()).getUsername()).isEqualTo(testUser.getEmail()); + } + + @DisplayName("getUserId(): 토큰으로 유저 ID를 가져올 수 있다.") + @Test + void getUserId() { + // given : 테스트를 위해 사용자를 저장하고 토큰을 생성 + User testUser = userRepository.save(User.builder().email("user@example.com").build()); + String token = tokenProvider.generateToken(testUser, java.time.Duration.ofDays(1)); + // Duration expiredAt : java.time.Duration.ofDays(1)을 사용하여 토큰의 유효 기간을 1일로 설정 + // when : 생성된 토큰으로부터 사용자 ID를 추출 + Long userIdByToken = tokenProvider.getUserId(token); + + // then : 추출된 ID가 저장된 사용자의 ID와 일치하는지 확인 + assertThat(userIdByToken).isEqualTo(testUser.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/com/ssg/starroad/user/controller/TokenApiControllerTest.java b/src/test/java/com/ssg/starroad/user/controller/TokenApiControllerTest.java new file mode 100644 index 0000000..28f1a24 --- /dev/null +++ b/src/test/java/com/ssg/starroad/user/controller/TokenApiControllerTest.java @@ -0,0 +1,107 @@ +package com.ssg.starroad.user.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ssg.starroad.config.jwt.JwtFactory; +import com.ssg.starroad.config.jwt.JwtProperties; +import com.ssg.starroad.user.dto.CreateAccessTokenRequest; +import com.ssg.starroad.user.entity.RefreshToken; +import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.repository.RefreshTokenRepository; +import com.ssg.starroad.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.time.Instant; +import java.util.Map; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class TokenApiControllerTest { + + @Autowired + private MockMvc mockMvc; + // 웹 서버를 실행하지 않고도 스프링 MVC(웹 계층)의 동작을 시뮬레이션 할 수 있게 해주는 클래스 + + @Autowired + private ObjectMapper objectMapper; + // JSON 데이터와 Java 객체 간의 변환을 처리 + + @Autowired + private WebApplicationContext context; + // 스프링 애플리케이션 컨텍스트를 제공 + + @Autowired + private JwtProperties jwtProperties; + + @Autowired + private UserRepository userRepository; + + @Autowired + private RefreshTokenRepository refreshTokenRepository; + + // 테스트 실행 전에 필요한 초기 설정을 수행 + // MockMvc 인스턴스를 구성하고 데이터베이스를 초기화 + @BeforeEach + public void mockMvcSetUp() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + userRepository.deleteAll(); + refreshTokenRepository.deleteAll(); + } + + @DisplayName("createAccessToken: 새로운 액세스 토큰을 발급한다.") + @Test + public void createAccessToken() throws Exception { + // given + final String url = "/api/token/access-token"; + + // 테스트용 사용자를 생성하고 데이터베이스에 저장합 + User testUser = userRepository.save(User.builder() + .email("user@example.com") + .password("Valid123$") + .build()); + + // 테스트 사용자의 ID를 사용하여 리프레시 토큰을 생성 + String refreshTokenString = JwtFactory.builder() + .claims(Map.of("id", testUser.getId())) + .build() + .createToken(jwtProperties); + + Instant expiryDate = Instant.now().plusSeconds(3600); // 만료 시간 설정 + + // 생성된 리프레시 토큰과 만료 시간을 설정하여 RefreshToken 인스턴스를 생성하고 저장 + RefreshToken refreshToken = new RefreshToken(testUser, refreshTokenString, expiryDate); + refreshTokenRepository.save(refreshToken); + + CreateAccessTokenRequest request = new CreateAccessTokenRequest(refreshTokenString); + final String requestBody = objectMapper.writeValueAsString(request); + + // when + ResultActions resultActions = mockMvc.perform(post(url) + .contentType(MediaType.APPLICATION_JSON_VALUE) // 요청의 컨텐트 타입을 JSON으로 설정 + .content(requestBody)); // 요청 본문에 CreateAccessTokenRequest 객체를 JSON 문자열로 변환하여 전달 + + // then + resultActions + .andExpect(status().isOk()) // HTTP 응답 상태 코드가 200(OK)인지 확인 + .andExpect(jsonPath("$.accessToken").isNotEmpty()); + // 응답 JSON 본문에서 accessToken 필드가 비어 있지 않은지 확인 + // $.accessToken은 JSON 응답에서 accessToken 속성에 접근하기 위한 경로 + + } +} + +// 클라이언트가 유효한 리프레시 토큰을 제공했을 때, +// 서버가 새 액세스 토큰을 생성하고 올바르게 반환하는지를 검증하는 과정을 자동화 \ No newline at end of file From 23eefcc0d7d0b82fae9aaeb11633a6e89818e9aa Mon Sep 17 00:00:00 2001 From: mariasoo <9723maria@gmail.com> Date: Thu, 9 May 2024 10:54:53 +0900 Subject: [PATCH 02/49] oauth : google OAuth2 --- .../ssg/starroad/common/util/CookieUtil.java | 65 +++++++++++ .../starroad/common/util/KeyGenerator.java | 3 + .../starroad/common/util/TokenGenerator.java | 21 ++++ .../starroad/config/WebSecurityConfig.java | 14 ++- .../starroad/config/jwt/TokenProvider.java | 4 +- .../user/controller/UserController.java | 6 +- .../com/ssg/starroad/user/dto/UserDTO.java | 4 +- .../com/ssg/starroad/user/entity/User.java | 11 +- .../ssg/starroad/user/enums/ProviderType.java | 18 +++ .../user/service/CustomOAuth2UserService.java | 8 ++ .../user/service/RefreshTokenService.java | 3 + .../starroad/user/service/UserService.java | 2 + .../impl/CustomOAuth2UserServiceImpl.java | 106 ++++++++++++++++++ .../service/impl/RefreshTokenServiceImpl.java | 37 +++++- .../user/service/impl/UserServiceImpl.java | 11 +- 15 files changed, 302 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/ssg/starroad/common/util/CookieUtil.java create mode 100644 src/main/java/com/ssg/starroad/common/util/TokenGenerator.java create mode 100644 src/main/java/com/ssg/starroad/user/enums/ProviderType.java create mode 100644 src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java create mode 100644 src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java diff --git a/src/main/java/com/ssg/starroad/common/util/CookieUtil.java b/src/main/java/com/ssg/starroad/common/util/CookieUtil.java new file mode 100644 index 0000000..b6cc61f --- /dev/null +++ b/src/main/java/com/ssg/starroad/common/util/CookieUtil.java @@ -0,0 +1,65 @@ +package com.ssg.starroad.common.util; + +// OAuth2 인증 플로우에서 쿠키를 사용하는 주된 이유는 인증 과정에서의 상태 관리와 보안을 강화하기 위해 +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.util.SerializationUtils; + +import java.util.Base64; + +public class CookieUtil { + + // 새로운 쿠키를 생성하고 클라이언트에게 전송 + public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) { + Cookie cookie = new Cookie(name, value); + cookie.setPath("/"); // 모든 경로에서 사용 + cookie.setHttpOnly(true); // JavaScript를 통한 접근 방지 + cookie.setSecure(true); // HTTPS 통신에서만 쿠키 전송 + cookie.setMaxAge(maxAge); // 쿠키의 생명 주기 제어 + response.addCookie(cookie); // 응답에 쿠키를 추가하여 클라이언트에게 전송 + } + + // 지정된 이름의 쿠키를 삭제 + public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) { + Cookie[] cookies = request.getCookies(); + // 요청에서 쿠키 배열을 가져옴 쿠키가 없으면 메소드 종료 + if (cookies == null) { + return; + } + + for (Cookie cookie : cookies) { + if (name.equals(cookie.getName())) { + cookie.setValue(""); // 해당 쿠키의 값을 빈 문자열로 설정 + cookie.setPath("/"); + cookie.setMaxAge(0); // 0으로 설정하여 즉시 만료되게 + response.addCookie(cookie); + } + // 변경된 쿠키를 응답에 추가하여 클라이언트에게 전송, 쿠키를 삭제 + } + } + + // 객체를 직렬화한 후 Base64 인코딩된 문자열로 변환하여 반환 + // dbj : 직렬화할 객체 + public static String serialize(Object obj) { + return Base64.getUrlEncoder() + .encodeToString(SerializationUtils.serialize(obj)); + } + // SerializationUtils를 사용하여 객체를 바이트 배열로 직렬화 + // 직렬화된 데이터를 Base64 인코딩하여 안전하게 문자열 형태로 변환 + // HTTP 헤더나 쿠키 값으로 사용될 때 유용 + + // 쿠키에 저장된 Base64 인코딩된 문자열을 객체로 역직렬화 + // cookie: 역직렬화할 데이터를 포함하고 있는 쿠키 + // cls: 반환될 객체의 클래스 타입 + public static T deserialize(Cookie cookie, Class cls) { + return cls.cast( + SerializationUtils.deserialize( + Base64.getUrlDecoder().decode(cookie.getValue()) + ) + ); + } + // 쿠키에서 값을 가져와 Base64 디코딩을 수행하여 바이트 배열로 변환 + // SerializationUtils를 사용하여 바이트 배열을 객체로 역직렬화 + // 역직렬화된 객체를 지정된 클래스 타입으로 캐스팅하여 반환 +} diff --git a/src/main/java/com/ssg/starroad/common/util/KeyGenerator.java b/src/main/java/com/ssg/starroad/common/util/KeyGenerator.java index 5e28b9a..c1ca794 100644 --- a/src/main/java/com/ssg/starroad/common/util/KeyGenerator.java +++ b/src/main/java/com/ssg/starroad/common/util/KeyGenerator.java @@ -3,6 +3,9 @@ import java.security.SecureRandom; import java.util.Base64; +// 주로 암호화 또는 토큰 서명에 사용 +// JWT 토큰을 서명할 때 사용되는 비밀키는 해당 토큰의 보안을 유지하는 데 필수적 +// 토큰의 내용을 암호화하거나 서명하는 데 사용되어, 토큰이 조작되지 않았음을 검증 가능 public class KeyGenerator { public static void main(String[] args) { SecureRandom random = new SecureRandom(); diff --git a/src/main/java/com/ssg/starroad/common/util/TokenGenerator.java b/src/main/java/com/ssg/starroad/common/util/TokenGenerator.java new file mode 100644 index 0000000..0518ee2 --- /dev/null +++ b/src/main/java/com/ssg/starroad/common/util/TokenGenerator.java @@ -0,0 +1,21 @@ +package com.ssg.starroad.common.util; + +import java.security.SecureRandom; +import java.util.Base64; + +public class TokenGenerator { + private static final SecureRandom secureRandom = new SecureRandom(); + private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder(); + + public static String generateUniqueToken() { + byte[] randomBytes = new byte[24]; // 192 bits + secureRandom.nextBytes(randomBytes); + return base64Encoder.encodeToString(randomBytes); + } +} + +// SecureRandom을 사용하여 토큰을 생성하는 것은 토큰 값이 완전히 무작위이고, 외부에서 예측할 수 없게 만들기 +// SecureRandom은 암호학적으로 안전한 무작위 수 생성기로, 높은 수준의 무작위성을 보장 + +// TokenGenerator에서 고유한 리프레시 토큰을 생성하는 로직을 구성하는 것은 +// 보안, 데이터 무결성, 그리고 사용자 간의 간섭 방지를 위해 필수적 \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java index 60d9359..417ae1f 100644 --- a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java +++ b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java @@ -1,5 +1,7 @@ package com.ssg.starroad.config; +import com.ssg.starroad.config.jwt.TokenProvider; +import com.ssg.starroad.user.service.CustomOAuth2UserService; import com.ssg.starroad.user.service.UserService; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -8,17 +10,24 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class WebSecurityConfig { private final UserService userService; + private final CustomOAuth2UserService customOAuth2UserService; private final BCryptPasswordEncoder bCryptPasswordEncoder; + private final TokenProvider tokenProvider; +// private final OAuth2AuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequestRepository; +// private final OAuth2SuccessHandler oAuth2SuccessHandler; - public WebSecurityConfig(UserService userService, BCryptPasswordEncoder bCryptPasswordEncoder) { + public WebSecurityConfig(UserService userService, CustomOAuth2UserService customOAuth2UserService, BCryptPasswordEncoder bCryptPasswordEncoder, TokenProvider tokenProvider) { this.userService = userService; + this.customOAuth2UserService = customOAuth2UserService; this.bCryptPasswordEncoder = bCryptPasswordEncoder; + this.tokenProvider = tokenProvider; } @Bean @@ -29,6 +38,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 이러한 토큰 기반 인증은 사용자의 세션 상태를 서버에 저장하지 않기 때문에 "무상태(stateless)"로 간주 // 사용자가 직접 요청에 토큰을 첨부해야 하므로, 악의적인 사이트가 사용자를 속여 토큰을 사용하는 요청을 생성하기 어려움 // 이는 CSRF 공격을 자동으로 방지하는 효과를 가짐 + .addFilterBefore(new TokenAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class) + // HTTP 요청 헤더에서 JWT를 검색하고, 유효한 토큰이 있는 경우 해당 사용자의 인증 정보를 SecurityContext에 설정 + // UsernamePasswordAuthenticationFilter 전에 실행되어, 토큰을 통한 인증이 우선적으로 처리되도록 .authorizeRequests(auth -> auth .requestMatchers("/user/login", "/user/join").permitAll() .anyRequest().authenticated() diff --git a/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java index 7e9dfe9..9dd0ae1 100644 --- a/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java +++ b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java @@ -47,7 +47,7 @@ public String createToken(Date expiry, User user) { .setIssuedAt(now) // 토큰 발급 시간 .setExpiration(expiry) // 토큰 만료 시간 .setSubject(user.getEmail()) // 사용자 이메일 - .claim("id", user.getId()) // 추가 정보 클레임 id : 유저 아이디 + .claim("userId", user.getId()) // 추가 정보 클레임 id : 유저 아이디 // 토큰에 사용자 고유 id 정보 담아 토큰 해석 시 사용자 정보 접근에 용이 // 서명 비밀값과 합께 해시값을 HS256 방식으로 암호화 .signWith(key) @@ -84,7 +84,7 @@ public Authentication getAuthentication(String token) { // 토큰에서 사용자 id 추출 public Long getUserId(String token) { Claims claims = getClaims(token); - return claims.get("id", Long.class); + return claims.get("userId", Long.class); } // 토큰에서 클레임을 추출하는 보조 메소드 diff --git a/src/main/java/com/ssg/starroad/user/controller/UserController.java b/src/main/java/com/ssg/starroad/user/controller/UserController.java index a1fcb34..8ba9f63 100644 --- a/src/main/java/com/ssg/starroad/user/controller/UserController.java +++ b/src/main/java/com/ssg/starroad/user/controller/UserController.java @@ -4,7 +4,9 @@ import com.ssg.starroad.user.dto.LoginRequest; import com.ssg.starroad.user.dto.LoginResponse; import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.service.RefreshTokenService; import com.ssg.starroad.user.service.UserService; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.userdetails.UserDetails; @@ -21,10 +23,12 @@ public class UserController { private final UserService userService; private final TokenProvider tokenProvider; + private final RefreshTokenService refreshTokenService; - public UserController(UserService userService, TokenProvider tokenProvider) { + public UserController(UserService userService, TokenProvider tokenProvider, RefreshTokenService refreshTokenService) { this.userService = userService; this.tokenProvider = tokenProvider; + this.refreshTokenService = refreshTokenService; } @PostMapping("/login") diff --git a/src/main/java/com/ssg/starroad/user/dto/UserDTO.java b/src/main/java/com/ssg/starroad/user/dto/UserDTO.java index e9dcd0e..5e7f248 100644 --- a/src/main/java/com/ssg/starroad/user/dto/UserDTO.java +++ b/src/main/java/com/ssg/starroad/user/dto/UserDTO.java @@ -2,6 +2,7 @@ import com.ssg.starroad.user.enums.ActiveStatus; import com.ssg.starroad.user.enums.Gender; +import com.ssg.starroad.user.enums.ProviderType; import lombok.*; import java.time.LocalDate; @@ -21,7 +22,8 @@ public class UserDTO { private LocalDate birth; private String phone; private String imagePath; - private String provider; + private ProviderType providerType; + private String providerId; private int reviewExp; private int point; private ActiveStatus activeStatus; diff --git a/src/main/java/com/ssg/starroad/user/entity/User.java b/src/main/java/com/ssg/starroad/user/entity/User.java index c73cc77..aac7c03 100644 --- a/src/main/java/com/ssg/starroad/user/entity/User.java +++ b/src/main/java/com/ssg/starroad/user/entity/User.java @@ -3,6 +3,7 @@ import com.ssg.starroad.common.entity.BaseTimeEntity; import com.ssg.starroad.user.enums.ActiveStatus; import com.ssg.starroad.user.enums.Gender; +import com.ssg.starroad.user.enums.ProviderType; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Past; @@ -24,7 +25,10 @@ import static lombok.AccessLevel.PROTECTED; @Getter -@Builder +@Builder(toBuilder = true) +// User 객체의 현재 상태를 기반으로 새로운 User 객체를 쉽게 생성 +// 예를 들어, 특정 속성만 변경하고 나머지는 기존 값으로 유지하려는 경우, +// toBuilder() 메소드를 사용하여 빌더를 얻고, 변경할 속성만 설정한 다음 build() 메소드를 호출하면 됨 @Entity @NoArgsConstructor(access = PROTECTED) @AllArgsConstructor @@ -65,8 +69,8 @@ public class User extends BaseTimeEntity implements UserDetails { @Email(message = "유효한 이메일 주소여야 합니다.") private String email; - @Column(unique = true) - private String provider; // provider + providerId => unique + private ProviderType providerType; // 네이버, 카카오, 구글 + private String providerId; // (네이버, 카카오, 구글)에서 받아온 아이디 private int reviewExp; private int point; @@ -113,4 +117,5 @@ public boolean isEnabled() { // activeStatus가 ACTIVE인 경우에만 true 반환 return this.activeStatus == ActiveStatus.ACTIVE; } + } diff --git a/src/main/java/com/ssg/starroad/user/enums/ProviderType.java b/src/main/java/com/ssg/starroad/user/enums/ProviderType.java new file mode 100644 index 0000000..23e4492 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/enums/ProviderType.java @@ -0,0 +1,18 @@ +package com.ssg.starroad.user.enums; + +public enum ProviderType { + NAVER, KAKAO, GOOGLE; + + // 입력받은 문자열 providerId를 대문자로 변환 + // ProviderType의 valueOf 메소드를 사용하여 해당 문자열과 일치하는 열거형 상수를 반환 + public static ProviderType fromString(String providerId) { + if (providerId == null) { + throw new IllegalArgumentException("providerId is null"); + } + try { + return ProviderType.valueOf(providerId.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("providerId에 맞는 열거형 값이 없음: " + providerId, e); + } + } +} diff --git a/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java b/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java new file mode 100644 index 0000000..abcf5e5 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java @@ -0,0 +1,8 @@ +package com.ssg.starroad.user.service; + +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; + +public interface CustomOAuth2UserService { + OAuth2User loadUser(OAuth2UserRequest userRequest); +} diff --git a/src/main/java/com/ssg/starroad/user/service/RefreshTokenService.java b/src/main/java/com/ssg/starroad/user/service/RefreshTokenService.java index 297e98e..84f25a3 100644 --- a/src/main/java/com/ssg/starroad/user/service/RefreshTokenService.java +++ b/src/main/java/com/ssg/starroad/user/service/RefreshTokenService.java @@ -3,5 +3,8 @@ import com.ssg.starroad.user.entity.RefreshToken; public interface RefreshTokenService { + RefreshToken findByRefreshToken(String refreshToken); + + String createRefreshToken(Long userId); } diff --git a/src/main/java/com/ssg/starroad/user/service/UserService.java b/src/main/java/com/ssg/starroad/user/service/UserService.java index 0ee191a..a595fa5 100644 --- a/src/main/java/com/ssg/starroad/user/service/UserService.java +++ b/src/main/java/com/ssg/starroad/user/service/UserService.java @@ -8,6 +8,8 @@ public interface UserService extends UserDetailsService { User findById(Long userId); + User findByEmail(String email); + Long save(UserDTO userDTO); boolean validateUser(String email, String password); diff --git a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java new file mode 100644 index 0000000..2a6e154 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java @@ -0,0 +1,106 @@ +package com.ssg.starroad.user.service.impl; + +import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.enums.ActiveStatus; +import com.ssg.starroad.user.enums.ProviderType; +import com.ssg.starroad.user.repository.UserRepository; +import com.ssg.starroad.user.service.CustomOAuth2UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RequiredArgsConstructor +@Service +public class CustomOAuth2UserServiceImpl extends DefaultOAuth2UserService implements CustomOAuth2UserService { + private final UserRepository userRepository; + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) { + OAuth2User oAuth2User = super.loadUser(userRequest); + return saveOrUpdate(userRequest, oAuth2User); + } + // OAuth2 프로바이더에서 사용자 정보를 로드 + // super.loadUser(userRequest)를 호출하여 기본 OAuth2 사용자 정보를 가져오기 + + // 데이터베이스에서 사용자 이메일을 기준으로 기존 사용자를 검색 + // updateExistingUser를 호출하여 정보를 업데이트하고, + // 존재하지 않는다면 registerNewUser를 호출하여 새로운 사용자를 등록 + // 사용자 정보를 DefaultOAuth2User로 변환하여 반환 + private OAuth2User saveOrUpdate(OAuth2UserRequest userRequest, OAuth2User oAuth2User) { + String email = oAuth2User.getAttribute("email"); + User user = userRepository.findByEmail(email) + .map(existingUser -> updateExistingUser(existingUser, oAuth2User)) + .orElseGet(() -> registerNewUser(userRequest, oAuth2User)); + + Map attributes = buildAttributes(user); + List authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + + return new DefaultOAuth2User(authorities, attributes, "email"); + } + + private User registerNewUser(OAuth2UserRequest userRequest, OAuth2User oAuth2User) { + String providerId = userRequest.getClientRegistration().getRegistrationId(); + String email = oAuth2User.getAttribute("email"); + + if (email == null) { + throw new IllegalArgumentException("이메일은 필수 정보입니다."); + } + + ProviderType providerType = ProviderType.fromString(providerId); + // providerId를 ProviderType 열거형으로 변환 + + User newUser = User.builder() + .email(email) + .name(oAuth2User.getAttribute("name")) + .nickname(oAuth2User.getAttribute("nickname")) + .phone(oAuth2User.getAttribute("phone")) + .imagePath(oAuth2User.getAttribute("picture")) + .providerType(providerType) + .birth(null) // 필요한 경우 적절한 로직으로 채우기 + .providerId(providerId) + .reviewExp(0) + .point(0) + .activeStatus(ActiveStatus.ACTIVE) + .build(); + + return userRepository.save(newUser); + } + + private User updateExistingUser(User existingUser, OAuth2User oAuth2User) { + User updatedUser = existingUser.toBuilder() + .name(oAuth2User.getAttribute("name")) + .nickname(oAuth2User.getAttribute("nickname")) + .phone(oAuth2User.getAttribute("phone")) + .build(); + + return userRepository.save(updatedUser); + } + + private Map buildAttributes(User user) { + Map attributes = new HashMap<>(); + attributes.put("name", user.getName()); + attributes.put("email", user.getEmail()); + attributes.put("nickname", user.getNickname()); + attributes.put("gender", user.getGender().name()); + attributes.put("birth", user.getBirth()); + attributes.put("phone", user.getPhone()); + attributes.put("imagePath", user.getImagePath()); + attributes.put("providerType", user.getProviderType()); + attributes.put("providerId", user.getProviderId()); + attributes.put("reviewExp", user.getReviewExp()); + attributes.put("point", user.getPoint()); + attributes.put("activeStatus", user.getActiveStatus().name()); + return attributes; + } +} + diff --git a/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java index ed5b862..68cd7c3 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java @@ -1,22 +1,57 @@ package com.ssg.starroad.user.service.impl; +import com.ssg.starroad.common.util.TokenGenerator; import com.ssg.starroad.user.entity.RefreshToken; +import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.repository.RefreshTokenRepository; +import com.ssg.starroad.user.repository.UserRepository; import com.ssg.starroad.user.service.RefreshTokenService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.time.Instant; + @RequiredArgsConstructor @Service public class RefreshTokenServiceImpl implements RefreshTokenService { private final RefreshTokenRepository refreshTokenRepository; + private final UserRepository userRepository; + + // RefreshToken에서 직접적으로 userId를 접근할 필요 없이 관련된 User 객체를 통해 필요한 정보 얻을 수 있음 @Override public RefreshToken findByRefreshToken(String refreshToken) { return refreshTokenRepository.findByToken(refreshToken) .orElseThrow(() -> new IllegalArgumentException("Unexpected token")); } + + // 사용자가 로그인할 때마다 이 메소드를 호출하여 + // 사용자의 세션에 대한 유효성을 연장하기 위한 새로운 리프레시 토큰을 발급 + @Override + public String createRefreshToken(Long userId) { + // User 객체를 가져오는 로직 + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("User not found with id: " + userId)); + + // 리프레시 토큰의 만료 시간 설정 + Instant expiryDate = Instant.now().plusSeconds(7 * 24 * 3600); // 7일 후 만료 + + // 새로운 RefreshToken 객체 생성 + RefreshToken refreshToken = new RefreshToken(user, generateUniqueToken(), expiryDate); + + // 생성된 리프레시 토큰을 데이터베이스에 저장 + refreshTokenRepository.save(refreshToken); + + return refreshToken.getToken(); // 직접 토큰 문자열 반환 + } + + // 고유한 토큰 값을 생성하는 데 사용. 특히 리프레시 토큰 내에서 고유성을 보장하는 데 사용 + // SecureRandom을 사용하여 예측이 불가능한 안전한 토큰을 생성 + // 이 토큰은 주로 리프레시 토큰의 일부로 사용되어 토큰 자체의 유일성을 보장 + public String generateUniqueToken() { + return TokenGenerator.generateUniqueToken(); + } } -// RefreshToken에서 직접적으로 userId를 접근할 필요 없이 관련된 User 객체를 통해 필요한 정보 얻을 수 있음 + diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index 4fc0018..afbd7f1 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -39,7 +39,13 @@ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundExcep @Override public User findById(Long userId) { return userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("Unexpected user")); + .orElseThrow(() -> new IllegalArgumentException("User not found with id: " + userId)); + } + + @Override + public User findByEmail(String email) { + return userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); } @@ -54,7 +60,8 @@ public Long save(UserDTO userDTO) { .gender(userDTO.getGender()) .birth(userDTO.getBirth()) .phone(userDTO.getPhone()) - .provider(userDTO.getProvider()) + .providerType(userDTO.getProviderType()) + .providerId(userDTO.getProviderId()) .reviewExp(userDTO.getReviewExp()) .point(userDTO.getPoint()) .activeStatus(userDTO.getActiveStatus()) From df2d7b8c209c7e812c0a879e890423903f4b8ffa Mon Sep 17 00:00:00 2001 From: mariasoo <9723maria@gmail.com> Date: Mon, 13 May 2024 16:58:02 +0900 Subject: [PATCH 03/49] OAuth2 : google, kakao --- .../ssg/starroad/common/util/CookieUtil.java | 1 + .../com/ssg/starroad/config/CorsConfig.java | 36 +++++ .../starroad/config/WebSecurityConfig.java | 109 +++++++++---- .../starroad/config/jwt/TokenProvider.java | 2 +- ...izationRequestBasedOnCookieRepository.java | 47 ++++++ .../config/oauth/OAuth2SuccessHandler.java | 87 +++++++++++ .../user/controller/OAuth2Controller.java | 27 ++++ .../user/controller/UserController.java | 23 ++- .../ssg/starroad/user/dto/LoginRequest.java | 5 + .../starroad/user/dto/OAuth2UserInfoDTO.java | 89 +++++++++++ .../com/ssg/starroad/user/entity/Manager.java | 7 +- .../com/ssg/starroad/user/entity/User.java | 49 +++++- .../repository/RefreshTokenRepository.java | 4 + .../user/service/CustomOAuth2UserService.java | 19 ++- .../starroad/user/service/UserService.java | 4 +- .../impl/CustomOAuth2UserServiceImpl.java | 101 +++++------- .../user/service/impl/UserServiceImpl.java | 41 +++-- .../user/service/oauth/OAuth2UserData.java | 9 ++ .../user/service/oauth/OAuth2UserInfo.java | 15 ++ .../ssg/starroad/config/jwt/JwtFactory.java | 2 +- .../config/jwt/TokenProviderTest.java | 28 +++- .../controller/TokenApiControllerTest.java | 19 ++- .../user/controller/UserControllerTest.java | 145 ++++++++++++++++++ .../user/service/RefreshTokenServiceTest.java | 4 + .../user/service/TokenServiceTest.java | 4 + 25 files changed, 740 insertions(+), 137 deletions(-) create mode 100644 src/main/java/com/ssg/starroad/config/CorsConfig.java create mode 100644 src/main/java/com/ssg/starroad/config/oauth/OAuth2AuthorizationRequestBasedOnCookieRepository.java create mode 100644 src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java create mode 100644 src/main/java/com/ssg/starroad/user/controller/OAuth2Controller.java create mode 100644 src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java create mode 100644 src/main/java/com/ssg/starroad/user/service/oauth/OAuth2UserData.java create mode 100644 src/main/java/com/ssg/starroad/user/service/oauth/OAuth2UserInfo.java create mode 100644 src/test/java/com/ssg/starroad/user/controller/UserControllerTest.java create mode 100644 src/test/java/com/ssg/starroad/user/service/RefreshTokenServiceTest.java create mode 100644 src/test/java/com/ssg/starroad/user/service/TokenServiceTest.java diff --git a/src/main/java/com/ssg/starroad/common/util/CookieUtil.java b/src/main/java/com/ssg/starroad/common/util/CookieUtil.java index b6cc61f..2519a82 100644 --- a/src/main/java/com/ssg/starroad/common/util/CookieUtil.java +++ b/src/main/java/com/ssg/starroad/common/util/CookieUtil.java @@ -19,6 +19,7 @@ public static void addCookie(HttpServletResponse response, String name, String v cookie.setMaxAge(maxAge); // 쿠키의 생명 주기 제어 response.addCookie(cookie); // 응답에 쿠키를 추가하여 클라이언트에게 전송 } + // HTTPOnly와 Secure 플래그를 가지게 설정하여 XSS와 데이터 중간자 공격을 방지 // 지정된 이름의 쿠키를 삭제 public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) { diff --git a/src/main/java/com/ssg/starroad/config/CorsConfig.java b/src/main/java/com/ssg/starroad/config/CorsConfig.java new file mode 100644 index 0000000..9db5d27 --- /dev/null +++ b/src/main/java/com/ssg/starroad/config/CorsConfig.java @@ -0,0 +1,36 @@ +package com.ssg.starroad.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +@Configuration +public class CorsConfig { + @Bean + public UrlBasedCorsConfigurationSource corsConfigurationSource() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOriginPattern("http://localhost:8080"); + config.addAllowedHeader("*"); // 서버가 받을 수 있는 모든 헤더를 정의 + + // 각 HTTP 메소드를 개별적으로 추가 + config.addAllowedMethod("GET"); + config.addAllowedMethod("POST"); + config.addAllowedMethod("PUT"); + config.addAllowedMethod("PATCH"); + config.addAllowedMethod("DELETE"); + config.addAllowedMethod("OPTIONS"); + + config.addExposedHeader("Authorization"); // 클라이언트가 읽을 수 있도록 서버의 응답에서 노출할 헤더를 정의 + config.addExposedHeader("refreshToken"); + source.registerCorsConfiguration("/**", config); // 모든 경로에 CORS 설정 적용 + return source; + } + @Bean + public CorsFilter corsFilter() { + return new CorsFilter(corsConfigurationSource()); + } +} diff --git a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java index 417ae1f..b595c66 100644 --- a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java +++ b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java @@ -1,70 +1,111 @@ package com.ssg.starroad.config; import com.ssg.starroad.config.jwt.TokenProvider; +import com.ssg.starroad.config.oauth.OAuth2AuthorizationRequestBasedOnCookieRepository; +import com.ssg.starroad.config.oauth.OAuth2SuccessHandler; +import com.ssg.starroad.user.repository.RefreshTokenRepository; import com.ssg.starroad.user.service.CustomOAuth2UserService; import com.ssg.starroad.user.service.UserService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import java.io.IOException; + @Configuration @EnableWebSecurity -public class WebSecurityConfig { +@RequiredArgsConstructor +public class WebSecurityConfig{ + private final TokenProvider tokenProvider; private final UserService userService; + private final RefreshTokenRepository refreshTokenRepository; private final CustomOAuth2UserService customOAuth2UserService; - private final BCryptPasswordEncoder bCryptPasswordEncoder; - private final TokenProvider tokenProvider; -// private final OAuth2AuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequestRepository; -// private final OAuth2SuccessHandler oAuth2SuccessHandler; + private final OAuth2SuccessHandler oAuth2SuccessHandler; + private final CorsConfig corsConfig; // CORS 설정을 위해 CorsConfig 클래스 주입 - public WebSecurityConfig(UserService userService, CustomOAuth2UserService customOAuth2UserService, BCryptPasswordEncoder bCryptPasswordEncoder, TokenProvider tokenProvider) { - this.userService = userService; - this.customOAuth2UserService = customOAuth2UserService; - this.bCryptPasswordEncoder = bCryptPasswordEncoder; - this.tokenProvider = tokenProvider; + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { // security를 적용하지 않을 리소스 + return web -> web.ignoring() + // error endpoint를 열어줌 + .requestMatchers("/error","/favicon.ico","/user/login"); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http - .csrf(csrf -> csrf.disable()) // CSRF 비활성화!!! - // API는 일반적으로 토큰 기반 인증, 예를 들어 JWT (JSON Web Tokens) 같은 시스템을 사용 - // 이러한 토큰 기반 인증은 사용자의 세션 상태를 서버에 저장하지 않기 때문에 "무상태(stateless)"로 간주 - // 사용자가 직접 요청에 토큰을 첨부해야 하므로, 악의적인 사이트가 사용자를 속여 토큰을 사용하는 요청을 생성하기 어려움 - // 이는 CSRF 공격을 자동으로 방지하는 효과를 가짐 - .addFilterBefore(new TokenAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class) - // HTTP 요청 헤더에서 JWT를 검색하고, 유효한 토큰이 있는 경우 해당 사용자의 인증 정보를 SecurityContext에 설정 - // UsernamePasswordAuthenticationFilter 전에 실행되어, 토큰을 통한 인증이 우선적으로 처리되도록 + .cors(cors -> cors.configurationSource(corsConfig.corsConfigurationSource())) + .csrf(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .logout(AbstractHttpConfigurer::disable) + .sessionManagement(sessionManagementConfig -> sessionManagementConfig + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeRequests(auth -> auth - .requestMatchers("/user/login", "/user/join").permitAll() - .anyRequest().authenticated() - ) - .formLogin(form -> form + .requestMatchers("/user/login", "/user/join", "/oauth2/**", "/api/token").permitAll() + .anyRequest().authenticated()) + .oauth2Login(oauth -> oauth .loginPage("/user/login") - .defaultSuccessUrl("/main", true) - .permitAll() - ) - .logout(logout -> logout - .logoutUrl("/user/logout") // 로그아웃 경로 설정 - .logoutSuccessUrl("/user/login") - .invalidateHttpSession(true) // 세션 무효화 - .deleteCookies("JSESSIONID") // 쿠키 삭제 - // JSESSIONID : 세션 식별자(서버가 사용자의 세션을 관리하는 데 사용) - // 로그아웃 시 사용자의 브라우저에서 서버에 대해 인증 없이 재접속하는 것을 방지하기 위함 - .permitAll() - ); + .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) + .successHandler(oAuth2SuccessHandler)) + .addFilterBefore(new TokenAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class) + .exceptionHandling(exceptions -> exceptions + .authenticationEntryPoint(this::authenticationEntryPoint) + .accessDeniedHandler(this::accessDeniedHandler)); return http.build(); } + private void authenticationEntryPoint(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + } + + private void accessDeniedHandler(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"); + } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } + + @Bean + public OAuth2SuccessHandler oAuth2SuccessHandler() { + return new OAuth2SuccessHandler(tokenProvider, customOAuth2UserService); + } + + @Bean + public TokenAuthenticationFilter tokenAuthenticationFilter() { + return new TokenAuthenticationFilter(tokenProvider); + } + + @Bean + public OAuth2AuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequestBasedOnCookieRepository() { + return new OAuth2AuthorizationRequestBasedOnCookieRepository(); + } + + @Bean + public AuthenticationManager authManager(HttpSecurity http) throws Exception { + AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); + authenticationManagerBuilder + .userDetailsService(userService) + .passwordEncoder(passwordEncoder()); + return authenticationManagerBuilder.build(); + } } \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java index 9dd0ae1..034fd1f 100644 --- a/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java +++ b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java @@ -30,7 +30,7 @@ public String generateToken(User user, Duration expiredAt) { } // 사용자 객체와 만료 시간(Duration)을 받아 jwt 토큰 생성 // 파라미터 (사용자 정보, 토큰 유효 기간) - // 현재 시간을 구하고 expiredAt으로 전달된 기간을 현재 시간에 더해 토큰 만료 시간expiry 계산 + // 현재 시간을 구하고 expiredAt으로 전달된 기간을 현재 시간에 더해 토큰 만료 시간 expiry 계산 // creatToken 메소드 호출 실제 토큰 생성 diff --git a/src/main/java/com/ssg/starroad/config/oauth/OAuth2AuthorizationRequestBasedOnCookieRepository.java b/src/main/java/com/ssg/starroad/config/oauth/OAuth2AuthorizationRequestBasedOnCookieRepository.java new file mode 100644 index 0000000..6524adc --- /dev/null +++ b/src/main/java/com/ssg/starroad/config/oauth/OAuth2AuthorizationRequestBasedOnCookieRepository.java @@ -0,0 +1,47 @@ +package com.ssg.starroad.config.oauth; + +import com.ssg.starroad.common.util.CookieUtil; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.web.util.WebUtils; + +public class OAuth2AuthorizationRequestBasedOnCookieRepository implements AuthorizationRequestRepository { + + public final static String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; + private final static int COOKIE_EXPIRE_SECONDS = 180; + + @Override + public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { + Cookie cookie = WebUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); + return CookieUtil.deserialize(cookie, OAuth2AuthorizationRequest.class); + } + + @Override + public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) { + if (authorizationRequest == null) { + removeAuthorizationRequestCookies(request, response); + return; + } + + CookieUtil.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtil.serialize(authorizationRequest), COOKIE_EXPIRE_SECONDS); + } + + @Override + public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) { + return this.loadAuthorizationRequest(request); + } + + public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) { + CookieUtil.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); + } +} + +// 쿠키 삭제 메소드: removeAuthorizationRequest 메소드와 removeAuthorizationRequestCookies 메소드 +// 모두 CookieUtil.deleteCookie를 사용하여 쿠키를 삭제 -> 코드의 일관성과 재사용성을 높임 +// 예외 처리 및 유효성 검증: loadAuthorizationRequest 메소드에서 쿠키가 null일 경우 적절하게 null을 반환하도록 처리 +// NullPointerException을 방지 +// 직렬화 및 역직렬화 로직의 캡슐화: CookieUtil 클래스의 serialize 및 deserialize 메소드를 사용하여 +// OAuth2 인증 요청 객체의 직렬화와 역직렬화를 수행 -> 보안을 강화하고 코드의 유지보수를 용이 diff --git a/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java b/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java new file mode 100644 index 0000000..55d17a4 --- /dev/null +++ b/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java @@ -0,0 +1,87 @@ +package com.ssg.starroad.config.oauth; + +import com.ssg.starroad.common.util.CookieUtil; +import com.ssg.starroad.config.jwt.TokenProvider; +import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.service.CustomOAuth2UserService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.IOException; +import java.time.Duration; + +@Component +public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private final TokenProvider tokenProvider; + private final CustomOAuth2UserService customOAuth2UserService; + // 액세스 토큰 및 리프레시 토큰 생성과 사용자 정보 조회 기능 활용 + // CustomOAuth2UserService를 사용하여 OAuth2 로그인 성공 후 사용자 정보를 처리 + + private static final String REFRESH_TOKEN_COOKIE_NAME = "refresh_token"; + private static final String ACCESS_TOKEN_COOKIE_NAME = "access_token"; + private static final int REFRESH_TOKEN_EXPIRY = 60 * 60 * 24 * 14; + // "60초 * 60분 * 24시간 * 14일" 결과적으로 리프레시 토큰의 유효 기간은 14일(두 주) + // (액세스 토큰보다 긴 유효 기간)사용자가 액세스 토큰이 만료된 후에도 새로운 액세스 토큰을 얻을 수 있음 + + private static final int ACCESS_TOKEN_EXPIRY = 60 * 60 * 24; + // "60초 * 60분 * 24시간" 결과적으로 액세스 토큰의 유효 기간은 24시간, 즉 1일 + // (리소스에 대한 접근을 제어하는 짧은 유효 기간의 토큰) 사용자가 인증된 상태로 서비스를 이용할 수 있게 함 + + public OAuth2SuccessHandler(TokenProvider tokenProvider, CustomOAuth2UserService customOAuth2UserService) { + this.tokenProvider = tokenProvider; + this.customOAuth2UserService = customOAuth2UserService; + } + + + // OAuth2 인증 과정이 성공적으로 완료된 후 실행되는 메소드 + // 1) 사용자 인증 정보를 활용하여 토큰을 생성하고, + // 2) 이를 클라이언트 측에 전달하기 위해 쿠키에 저장 + // 3) 사용자를 최종 목적지 URL로 리디렉션 + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + try { + OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; + OAuth2User oAuth2User = oauthToken.getPrincipal(); + + // 이메일을 통해 사용자 정보 조회 + User user = customOAuth2UserService.findUserByEmail(oAuth2User.getAttribute("email")); + + // 토큰 생성 + String refreshToken = tokenProvider.generateToken(user, Duration.ofSeconds(REFRESH_TOKEN_EXPIRY)); + String accessToken = tokenProvider.generateToken(user, Duration.ofSeconds(ACCESS_TOKEN_EXPIRY)); + + // 쿠키에 토큰 추가 + addCookie(response, REFRESH_TOKEN_COOKIE_NAME, refreshToken, REFRESH_TOKEN_EXPIRY); + addCookie(response, ACCESS_TOKEN_COOKIE_NAME, accessToken, ACCESS_TOKEN_EXPIRY); + + // 최종 목적지 URL로 리다이렉트 + String targetUrl = determineTargetUrl(request, response, authentication); + clearAuthenticationAttributes(request); + getRedirectStrategy().sendRedirect(request, response, targetUrl); + } catch (Exception e) { + throw new RuntimeException("OAuth2 인증 성공 처리 중 오류 발생", e); + } + } + + // 지정된 이름, 값, 최대 유지 시간을 가진 쿠키를 HTTP 응답에 추가하는 메소드 + // 인증 토큰을 클라이언트에게 안전하게 전달하는 데 사용 + // addCookie 메소드 하나만 있어도 충분히 토큰 종류(액세스/리프레시)를 구별하여 쿠키에 저장 + private void addCookie(HttpServletResponse response, String name, String value, int maxAge) { + CookieUtil.addCookie(response,name, value, maxAge); + } + + // 인증 성공 후 리디렉션할 최종 URL을 결정하는 메소드 + // URL은 사용자가 인증 후 이동할 최종 목적지를 지정 + // UriComponentsBuilder를 사용하여 리디렉션할 URL을 구성하고, 이를 문자열로 반환 + protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + return UriComponentsBuilder.fromUriString("/main") + .build().toUriString(); + } +} diff --git a/src/main/java/com/ssg/starroad/user/controller/OAuth2Controller.java b/src/main/java/com/ssg/starroad/user/controller/OAuth2Controller.java new file mode 100644 index 0000000..06c311a --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/controller/OAuth2Controller.java @@ -0,0 +1,27 @@ +package com.ssg.starroad.user.controller; + +import com.ssg.starroad.user.service.CustomOAuth2UserService; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/oauth2") +public class OAuth2Controller { + + private final CustomOAuth2UserService customOAuth2UserService; + + public OAuth2Controller(CustomOAuth2UserService customOAuth2UserService) { + this.customOAuth2UserService = customOAuth2UserService; + } + + @GetMapping("/callback") + public String handleOAuth2Callback(OAuth2AuthenticationToken authentication) { + // OAuth2AuthenticationToken에서 직접 OAuth2User를 받아오기 + OAuth2User oAuth2User = authentication.getPrincipal(); + customOAuth2UserService.processOAuthPostLogin(oAuth2User); + return "redirect:/main"; + } +} diff --git a/src/main/java/com/ssg/starroad/user/controller/UserController.java b/src/main/java/com/ssg/starroad/user/controller/UserController.java index 8ba9f63..407eecc 100644 --- a/src/main/java/com/ssg/starroad/user/controller/UserController.java +++ b/src/main/java/com/ssg/starroad/user/controller/UserController.java @@ -6,7 +6,7 @@ import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.service.RefreshTokenService; import com.ssg.starroad.user.service.UserService; -import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.userdetails.UserDetails; @@ -36,15 +36,22 @@ public ResponseEntity login(@RequestBody LoginRequest request) { boolean isValidUser = userService.validateUser(request.getEmail(), request.getPassword()); if (isValidUser) { UserDetails userDetails = userService.loadUserByUsername(request.getEmail()); - User user = (User) userDetails; // UserDetails를 User로 캐스팅 + User user = userService.findByEmail(userDetails.getUsername()).orElse(null); + if (user == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } - // 토큰 만료 시간 설정: 현재 시간으로부터 10분 후 - Date expiry = new Date(System.currentTimeMillis() + 600 * 1000); // 600 seconds (10 minutes) + Date expiry = new Date(System.currentTimeMillis() + 600 * 1000); // 10 minutes + String accessToken = tokenProvider.createToken(expiry, user); + String refreshToken = refreshTokenService.createRefreshToken(user.getId()); - String accessToken = tokenProvider.createToken(expiry, user); // 만료 시간을 인자로 추가 - String refreshToken = refreshTokenService.createRefreshToken(user.getId()); // 리프레시 토큰 생성 로직 - return ResponseEntity.ok(new LoginResponse(accessToken, refreshToken)); + // 설정된 헤더를 응답에 추가 + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + accessToken); // Bearer 타입의 Authorization 헤더 추가 + + LoginResponse loginResponse = new LoginResponse(accessToken, refreshToken); + return ResponseEntity.ok().headers(headers).body(loginResponse); } return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/dto/LoginRequest.java b/src/main/java/com/ssg/starroad/user/dto/LoginRequest.java index c5da68d..7a1073f 100644 --- a/src/main/java/com/ssg/starroad/user/dto/LoginRequest.java +++ b/src/main/java/com/ssg/starroad/user/dto/LoginRequest.java @@ -10,4 +10,9 @@ public class LoginRequest { private String email; private String password; + + public LoginRequest(String email, String password) { + this.email = email; + this.password = password; + } } diff --git a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java new file mode 100644 index 0000000..539a0c4 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java @@ -0,0 +1,89 @@ +package com.ssg.starroad.user.dto; + +import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.enums.ActiveStatus; +import com.ssg.starroad.user.enums.ProviderType; +import lombok.*; +import org.springframework.security.oauth2.core.user.OAuth2User; + +import java.util.Map; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2UserInfoDTO { + private String email; + private String name; + private String nickname; + private String imagePath; + private ProviderType providerType; + private String providerId; + + // OAuth2User 인터페이스를 구현한 객체에서 필요한 정보를 추출하여 OAuth2UserInfoDTO 객체를 생성 + // OAuth2 인증 과정에서 제공된 사용자 정보 중 필요한 부분만을 선택하여 DTO에 저장 + public static OAuth2UserInfoDTO fromOAuth2User(OAuth2User oAuth2User, String providerType, String providerId) { + switch (providerType.toUpperCase()) { + case "GOOGLE": + return createGoogleDTO(oAuth2User, providerId); + case "KAKAO": + return createKakaoDTO(oAuth2User, providerId); + case "NAVER": + return createNaverDTO(oAuth2User, providerId); + default: + throw new IllegalArgumentException("Unsupported provider: " + providerType); + } + } + private static OAuth2UserInfoDTO createGoogleDTO(OAuth2User oAuth2User, String providerId) { + OAuth2UserInfoDTO dto = new OAuth2UserInfoDTO(); + dto.setEmail(oAuth2User.getAttribute("email")); + dto.setName(oAuth2User.getAttribute("name")); + dto.setImagePath(oAuth2User.getAttribute("picture")); + dto.setProviderType(ProviderType.GOOGLE); + dto.setProviderId(providerId); + return dto; + } + + private static OAuth2UserInfoDTO createKakaoDTO(OAuth2User oAuth2User, String providerId) { + Map kakaoAccount = oAuth2User.getAttribute("kakao_account"); + OAuth2UserInfoDTO dto = new OAuth2UserInfoDTO(); + if (kakaoAccount != null) { + dto.setEmail((String) kakaoAccount.get("email")); + Map profile = (Map) kakaoAccount.get("profile"); + dto.setName((String) profile.get("nickname")); + dto.setImagePath((String) profile.get("thumbnail_image_url")); + } + dto.setProviderType(ProviderType.KAKAO); + dto.setProviderId(providerId); + return dto; + } + + private static OAuth2UserInfoDTO createNaverDTO(OAuth2User oAuth2User, String providerId) { + Map response = oAuth2User.getAttribute("response"); + OAuth2UserInfoDTO dto = new OAuth2UserInfoDTO(); + if (response != null) { + dto.setEmail((String) response.get("email")); + dto.setName((String) response.get("name")); + dto.setImagePath((String) response.get("profile_image")); + } + dto.setProviderType(ProviderType.NAVER); + dto.setProviderId(providerId); + return dto; + } + + // User 엔티티의 새 인스턴스를 생성 + // DTO가 서비스 레이어와 데이터 계층 사이에서 엔티티의 생성을 담당 + // 엔티티 인스턴스의 생성 로직과 비즈니스 로직을 분리 + public User toEntity() { + return User.builder() + .email(this.email) + .name(this.name) + .nickname(this.nickname) + .imagePath(this.imagePath) + .providerType(this.providerType) + .providerId(this.providerId) + .activeStatus(ActiveStatus.ACTIVE) + .build(); + } +} diff --git a/src/main/java/com/ssg/starroad/user/entity/Manager.java b/src/main/java/com/ssg/starroad/user/entity/Manager.java index 014c6f3..f3dc8bb 100644 --- a/src/main/java/com/ssg/starroad/user/entity/Manager.java +++ b/src/main/java/com/ssg/starroad/user/entity/Manager.java @@ -2,10 +2,7 @@ import com.ssg.starroad.common.entity.BaseTimeEntity; import com.ssg.starroad.user.enums.Authority; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; +import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; @@ -27,5 +24,7 @@ public class Manager extends BaseTimeEntity { private String name; @Column(unique = true) private String businessNumber; + + @Enumerated(EnumType.STRING) private Authority authority; } diff --git a/src/main/java/com/ssg/starroad/user/entity/User.java b/src/main/java/com/ssg/starroad/user/entity/User.java index aac7c03..bcb745a 100644 --- a/src/main/java/com/ssg/starroad/user/entity/User.java +++ b/src/main/java/com/ssg/starroad/user/entity/User.java @@ -16,10 +16,13 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.user.OAuth2User; import java.time.LocalDate; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; @@ -32,7 +35,7 @@ @Entity @NoArgsConstructor(access = PROTECTED) @AllArgsConstructor -public class User extends BaseTimeEntity implements UserDetails { +public class User extends BaseTimeEntity implements UserDetails, OAuth2User { @Id @GeneratedValue(strategy = IDENTITY) @@ -41,8 +44,8 @@ public class User extends BaseTimeEntity implements UserDetails { // 데이터베이스에서 식별자로 사용되는 id 값의 안정성을 보장 private Long id; - @Size(min = 8, max = 15, message = "비밀번호는 8자 이상 15자 이하여야 합니다.") - @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,}$") + @Pattern(regexp = "^(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W).{8,}$", + message = "비밀번호는 8자 이상이며, 숫자, 문자, 특수문자를 각각 하나 이상 포함해야 합니다.") private String password; // bcrypt @Size(min = 2, max = 10, message = "이름은 2자 이상 10자 이하여야 합니다.") @@ -69,6 +72,7 @@ public class User extends BaseTimeEntity implements UserDetails { @Email(message = "유효한 이메일 주소여야 합니다.") private String email; + @Enumerated(EnumType.STRING) private ProviderType providerType; // 네이버, 카카오, 구글 private String providerId; // (네이버, 카카오, 구글)에서 받아온 아이디 @@ -79,6 +83,45 @@ public class User extends BaseTimeEntity implements UserDetails { private ActiveStatus activeStatus; + // OAuth2User 메소드 + + @Override + public String getName() { + return this.providerId; // providerId를 반환 + } + + // 제네릭 타입 A를 사용하여, OAuth2User 인터페이스에서 + // 다양한 타입의 사용자 속성을 안전하게 가져오도록 설계(필수) + // String email = user.getAttribute("email")과 같이 사용할 때 + // 컴파일러가 반환 타입이 String임을 확인하고, 다른 타입으로 잘못 캐스팅하는 실수를 방지 + // 또 name 파라미터는 OAuth2User의 속성 중 하나를 지정하는 문자열 + // email, name, nickname 등과 같이 실제 속성 이름을 문자열로 전달받아 해당 속성 값을 반환 + @Override + public A getAttribute(String name) { + switch (name) { + case "email": return (A) this.email; + case "name": return (A) this.name; + case "nickname": return (A) this.nickname; + case "imagePath": return (A) this.imagePath; + default: return null; // 해당하는 속성이 없을 경우 null 반환 + } + } + + // 메소드는 속성 맵 전체를 반환 + @Override + public Map getAttributes() { + // 실제 사용 시 필요한 모든 속성을 포함한 Map을 반환 + Map attributes = new HashMap<>(); + attributes.put("email", this.email); + attributes.put("name", this.name); + attributes.put("nickname", this.nickname); + attributes.put("imagePath", this.imagePath); + attributes.put("providerType", this.providerType != null ? this.providerType.name() : null); + attributes.put("providerId", this.providerId); + return attributes; + } + + // UserDetails 메소드 @Override public Collection getAuthorities() { // 모든 사용자에게 'ROLE_USER' 권한 부여 diff --git a/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java b/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java index a822640..b892ee1 100644 --- a/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java +++ b/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java @@ -2,6 +2,7 @@ import com.ssg.starroad.user.entity.RefreshToken; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @@ -9,6 +10,9 @@ public interface RefreshTokenRepository extends JpaRepository { Optional findByUser_Id(Long userId); Optional findByToken(String refreshToken); + + @Transactional + void deleteByUserId(Long userId); } // findByUser_Id(Long userId) 메서드를 사용하는 것은 diff --git a/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java b/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java index abcf5e5..fc7f394 100644 --- a/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java +++ b/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java @@ -1,8 +1,25 @@ package com.ssg.starroad.user.service; +import com.ssg.starroad.user.entity.User; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -public interface CustomOAuth2UserService { +public interface CustomOAuth2UserService extends OAuth2UserService { OAuth2User loadUser(OAuth2UserRequest userRequest); + + User findUserByEmail(String email); + + void processOAuthPostLogin(OAuth2User oAuth2User); } + +// OAuth2 프로세스는 사용자가 예를 들어 Google이나 Facebook 같은 외부 서비스를 사용하여 로그인할 때 발생함 +// 사용자가 이러한 서비스를 통해 로그인을 시도하면, +// 이 서비스는 사용자의 정보(예: 이름, 이메일, 프로필 사진 등)를 포함하는 OAuth2UserRequest를 생성하여 애플리케이션에 보냄 +// OAuth2UserRequest는 OAuth2UserService에 의해 처리되며, +// 이 서비스는 외부 시스템에서 제공하는 정보를 기반으로 OAuth2User 객체를 생성 +// 이 객체는 Spring Security 컨텍스트에서 사용자의 인증 정보로 활용 + +// OAuth2UserService를 상속받아 구현되어야 하는 이유 +// 외부 서비스로부터 받은 사용자 정보(OAuth2UserRequest)를 애플리케이션의 요구사항에 맞게 처리하고, +// 이를 OAuth2User 객체로 변환하여 Spring Security에 통합하기 위해 \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/service/UserService.java b/src/main/java/com/ssg/starroad/user/service/UserService.java index a595fa5..1e38fd6 100644 --- a/src/main/java/com/ssg/starroad/user/service/UserService.java +++ b/src/main/java/com/ssg/starroad/user/service/UserService.java @@ -4,11 +4,13 @@ import com.ssg.starroad.user.entity.User; import org.springframework.security.core.userdetails.UserDetailsService; +import java.util.Optional; + public interface UserService extends UserDetailsService { User findById(Long userId); - User findByEmail(String email); + Optional findByEmail(String email); Long save(UserDTO userDTO); diff --git a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java index 2a6e154..5974d6d 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java @@ -1,10 +1,10 @@ package com.ssg.starroad.user.service.impl; import com.ssg.starroad.user.entity.User; -import com.ssg.starroad.user.enums.ActiveStatus; import com.ssg.starroad.user.enums.ProviderType; import com.ssg.starroad.user.repository.UserRepository; import com.ssg.starroad.user.service.CustomOAuth2UserService; +import com.ssg.starroad.user.service.oauth.OAuth2UserData; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -22,58 +22,53 @@ @RequiredArgsConstructor @Service public class CustomOAuth2UserServiceImpl extends DefaultOAuth2UserService implements CustomOAuth2UserService { + private final UserRepository userRepository; + private final OAuth2UserData oauth2UserData; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) { OAuth2User oAuth2User = super.loadUser(userRequest); - return saveOrUpdate(userRequest, oAuth2User); - } - // OAuth2 프로바이더에서 사용자 정보를 로드 - // super.loadUser(userRequest)를 호출하여 기본 OAuth2 사용자 정보를 가져오기 + String provider = userRequest.getClientRegistration().getRegistrationId(); + String providerId = oAuth2User.getName(); + + User user = oauth2UserData.oauth2Info(oAuth2User, provider, providerId); + + if (user.getEmail() == null) { + throw new IllegalArgumentException("OAuth2 제공자로부터 이메일을 가져올 수 없습니다."); + } - // 데이터베이스에서 사용자 이메일을 기준으로 기존 사용자를 검색 - // updateExistingUser를 호출하여 정보를 업데이트하고, - // 존재하지 않는다면 registerNewUser를 호출하여 새로운 사용자를 등록 - // 사용자 정보를 DefaultOAuth2User로 변환하여 반환 - private OAuth2User saveOrUpdate(OAuth2UserRequest userRequest, OAuth2User oAuth2User) { - String email = oAuth2User.getAttribute("email"); - User user = userRepository.findByEmail(email) + User savedUser = userRepository.findByEmail(user.getEmail()) .map(existingUser -> updateExistingUser(existingUser, oAuth2User)) - .orElseGet(() -> registerNewUser(userRequest, oAuth2User)); + .orElseGet(() -> user); - Map attributes = buildAttributes(user); - List authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + userRepository.save(savedUser); + return createOAuth2User(savedUser); + } + private OAuth2User createOAuth2User(User user) { + Map attributes = new HashMap<>(); + attributes.put("email", user.getEmail()); + attributes.put("name", user.getName()); + attributes.put("nickname", user.getNickname()); + attributes.put("phone", user.getPhone()); + attributes.put("imagePath", user.getImagePath()); + List authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); return new DefaultOAuth2User(authorities, attributes, "email"); } + @Override + public User findUserByEmail(String email) { + return userRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("No user found with email " + email)); + } - private User registerNewUser(OAuth2UserRequest userRequest, OAuth2User oAuth2User) { - String providerId = userRequest.getClientRegistration().getRegistrationId(); - String email = oAuth2User.getAttribute("email"); - - if (email == null) { - throw new IllegalArgumentException("이메일은 필수 정보입니다."); - } - - ProviderType providerType = ProviderType.fromString(providerId); - // providerId를 ProviderType 열거형으로 변환 - - User newUser = User.builder() - .email(email) - .name(oAuth2User.getAttribute("name")) - .nickname(oAuth2User.getAttribute("nickname")) - .phone(oAuth2User.getAttribute("phone")) - .imagePath(oAuth2User.getAttribute("picture")) - .providerType(providerType) - .birth(null) // 필요한 경우 적절한 로직으로 채우기 - .providerId(providerId) - .reviewExp(0) - .point(0) - .activeStatus(ActiveStatus.ACTIVE) - .build(); - - return userRepository.save(newUser); + // OAuth2 로그인 후 사용자 처리 + @Override + public void processOAuthPostLogin(OAuth2User oAuth2User) { + String provider = oAuth2User.getAttribute("provider"); + String providerId = oAuth2User.getName(); + User user = oauth2UserData.oauth2Info(oAuth2User, provider, providerId); + userRepository.save(user); // 사용자 정보 저장 } private User updateExistingUser(User existingUser, OAuth2User oAuth2User) { @@ -81,26 +76,12 @@ private User updateExistingUser(User existingUser, OAuth2User oAuth2User) { .name(oAuth2User.getAttribute("name")) .nickname(oAuth2User.getAttribute("nickname")) .phone(oAuth2User.getAttribute("phone")) + .imagePath(oAuth2User.getAttribute("picture")) + .providerType(ProviderType.fromString(oAuth2User.getAttribute("provider"))) + .providerId(oAuth2User.getName()) .build(); - - return userRepository.save(updatedUser); - } - - private Map buildAttributes(User user) { - Map attributes = new HashMap<>(); - attributes.put("name", user.getName()); - attributes.put("email", user.getEmail()); - attributes.put("nickname", user.getNickname()); - attributes.put("gender", user.getGender().name()); - attributes.put("birth", user.getBirth()); - attributes.put("phone", user.getPhone()); - attributes.put("imagePath", user.getImagePath()); - attributes.put("providerType", user.getProviderType()); - attributes.put("providerId", user.getProviderId()); - attributes.put("reviewExp", user.getReviewExp()); - attributes.put("point", user.getPoint()); - attributes.put("activeStatus", user.getActiveStatus().name()); - return attributes; + userRepository.save(updatedUser); // 변경된 사용자 정보 저장 + return updatedUser; } } diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index afbd7f1..69f6a68 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -8,7 +8,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.util.Collections; @@ -19,22 +19,21 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Optional userOptional = userRepository.findByEmail(email); User user = userOptional.orElseThrow(() -> - new UsernameNotFoundException("해당 이메일을 가진 사용자를 찾을 수 없습니다: " + email)); + new UsernameNotFoundException("User not found with email: " + email)); return new org.springframework.security.core.userdetails.User( user.getEmail(), user.getPassword(), - Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); // 권한 설정 + Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); } // Collections.singletonList 단일 항목을 포함하는 변경 불가능한 리스트 생성. 단일 권한 처리 - // 리프레시 토큰을 이용해 새로운 액세스 토큰을 생성할 때 해당 사용자의 정보를 db에서 조회하려고 + // 리프레시 토큰을 이용해 새로운 액세스 토큰을 생성할 때 해당 사용자의 정보를 db에서 조회하기 위해 // User 클래스에서 id 필드에 해당하며, @Id 어노테이션으로 지정 @Override public User findById(Long userId) { @@ -42,18 +41,21 @@ public User findById(Long userId) { .orElseThrow(() -> new IllegalArgumentException("User not found with id: " + userId)); } + // OAuth2에서 제공하는 이메일은 유일 값이므로 이 메서드를 사용해 유저 찾기 가능 @Override - public User findByEmail(String email) { - return userRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("User not found with email: " + email)); + public Optional findByEmail(String email) { + return userRepository.findByEmail(email); } @Override public Long save(UserDTO userDTO) { + + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + User user = User.builder() .email(userDTO.getEmail()) - .password(passwordEncoder.encode(userDTO.getPassword())) + .password(encoder.encode(userDTO.getPassword())) .name(userDTO.getName()) .nickname(userDTO.getNickname()) .imagePath(userDTO.getImagePath()) @@ -69,12 +71,21 @@ public Long save(UserDTO userDTO) { return userRepository.save(user).getId(); } public boolean validateUser(String email, String password) { - Optional userOptional = userRepository.findByEmail(email); - if (userOptional.isPresent()) { - User user = userOptional.get(); - return passwordEncoder.matches(password, user.getPassword()) && user.isEnabled(); + + Optional userOpt = userRepository.findByEmail(email); + if (!userOpt.isPresent()) { + System.out.println("No user found with email: " + email); + return false; } - return false; - } + User user = userOpt.get(); + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + boolean passwordMatches = encoder.matches(password, user.getPassword()); + + if (!passwordMatches) { + System.out.println("Password mismatch for user: " + email); + } + + return passwordMatches; + } } diff --git a/src/main/java/com/ssg/starroad/user/service/oauth/OAuth2UserData.java b/src/main/java/com/ssg/starroad/user/service/oauth/OAuth2UserData.java new file mode 100644 index 0000000..4fd6deb --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/oauth/OAuth2UserData.java @@ -0,0 +1,9 @@ +package com.ssg.starroad.user.service.oauth; + +import com.ssg.starroad.user.entity.User; +import org.springframework.security.oauth2.core.user.OAuth2User; + + +public interface OAuth2UserData { + User oauth2Info(OAuth2User oAuth2User, String providerType, String providerId); +} diff --git a/src/main/java/com/ssg/starroad/user/service/oauth/OAuth2UserInfo.java b/src/main/java/com/ssg/starroad/user/service/oauth/OAuth2UserInfo.java new file mode 100644 index 0000000..1859722 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/oauth/OAuth2UserInfo.java @@ -0,0 +1,15 @@ +package com.ssg.starroad.user.service.oauth; + +import com.ssg.starroad.user.dto.OAuth2UserInfoDTO; +import com.ssg.starroad.user.entity.User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +@Service +public class OAuth2UserInfo implements OAuth2UserData { + @Override + public User oauth2Info(OAuth2User oAuth2User, String providerType, String providerId) { + OAuth2UserInfoDTO dto = OAuth2UserInfoDTO.fromOAuth2User(oAuth2User, providerType, providerId); + return dto.toEntity(); + } +} \ No newline at end of file diff --git a/src/test/java/com/ssg/starroad/config/jwt/JwtFactory.java b/src/test/java/com/ssg/starroad/config/jwt/JwtFactory.java index 9f843c9..275d58d 100644 --- a/src/test/java/com/ssg/starroad/config/jwt/JwtFactory.java +++ b/src/test/java/com/ssg/starroad/config/jwt/JwtFactory.java @@ -16,7 +16,7 @@ @Getter public class JwtFactory { - private String subject = "user@example.com"; + private String subject = "gildong@example.com"; private Date issuedAt = new Date(); private Date expiration = new Date(new Date().getTime() + Duration.ofDays(14).toMillis()); private Map claims = emptyMap(); diff --git a/src/test/java/com/ssg/starroad/config/jwt/TokenProviderTest.java b/src/test/java/com/ssg/starroad/config/jwt/TokenProviderTest.java index 3e1601c..092e869 100644 --- a/src/test/java/com/ssg/starroad/config/jwt/TokenProviderTest.java +++ b/src/test/java/com/ssg/starroad/config/jwt/TokenProviderTest.java @@ -2,22 +2,27 @@ import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.enums.ActiveStatus; +import com.ssg.starroad.user.enums.Gender; +import com.ssg.starroad.user.enums.ProviderType; import com.ssg.starroad.user.repository.UserRepository; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.test.annotation.Rollback; import org.springframework.transaction.annotation.Transactional; import java.time.Duration; +import java.time.LocalDate; import java.util.Date; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest -@Transactional class TokenProviderTest { @Autowired @@ -29,15 +34,23 @@ class TokenProviderTest { @Autowired private JwtProperties jwtProperties; + @BeforeEach @DisplayName("generateToken(): 유저 정보와 만료 기간을 전달해 토큰을 만들 수 있다.") @Test void generateToken() { // given : 테스트를 위한 사용자 정보를 설정하고 데이터베이스에 저장 User testUser = userRepository.save(User.builder() - .email("user@example.com") - .password("Valid123$") + .name("홍길동") + .nickname("gildong123") + .password("password@123") // 비밀번호 패턴을 준수해야 합니다. + .email("gildong@example.com") + .gender(Gender.MALE) + .birth(LocalDate.of(1990, 1, 1)) + .phone("01012345678") + .providerType(ProviderType.GOOGLE) + .providerId("1234567890") + .activeStatus(ActiveStatus.ACTIVE) .build()); - // when : 토큰 생성 메서드를 호출하여 JWT 토큰을 생성 String token = tokenProvider.generateToken(testUser, Duration.ofDays(14)); @@ -85,8 +98,8 @@ void validToken_validToken() { void getAuthentication() { // given : 인증 정보 생성을 위해 사용자를 저장하고 토큰을 생성 User testUser = userRepository.save(User.builder() - .email("user@example.com") - .password("Valid123$") + .email("gildong@example.com") + .password("password@123") .build()); // when : 생성된 토큰을 사용하여 인증 정보를 얻음 @@ -97,11 +110,12 @@ void getAuthentication() { assertThat(((UserDetails) authentication.getPrincipal()).getUsername()).isEqualTo(testUser.getEmail()); } + @Rollback(false) // 특정 테스트에서 롤백 비활성화 예시 @DisplayName("getUserId(): 토큰으로 유저 ID를 가져올 수 있다.") @Test void getUserId() { // given : 테스트를 위해 사용자를 저장하고 토큰을 생성 - User testUser = userRepository.save(User.builder().email("user@example.com").build()); + User testUser = userRepository.save(User.builder().email("gildong@example.com").build()); String token = tokenProvider.generateToken(testUser, java.time.Duration.ofDays(1)); // Duration expiredAt : java.time.Duration.ofDays(1)을 사용하여 토큰의 유효 기간을 1일로 설정 // when : 생성된 토큰으로부터 사용자 ID를 추출 diff --git a/src/test/java/com/ssg/starroad/user/controller/TokenApiControllerTest.java b/src/test/java/com/ssg/starroad/user/controller/TokenApiControllerTest.java index 28f1a24..b5e03ca 100644 --- a/src/test/java/com/ssg/starroad/user/controller/TokenApiControllerTest.java +++ b/src/test/java/com/ssg/starroad/user/controller/TokenApiControllerTest.java @@ -6,6 +6,9 @@ import com.ssg.starroad.user.dto.CreateAccessTokenRequest; import com.ssg.starroad.user.entity.RefreshToken; import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.enums.ActiveStatus; +import com.ssg.starroad.user.enums.Gender; +import com.ssg.starroad.user.enums.ProviderType; import com.ssg.starroad.user.repository.RefreshTokenRepository; import com.ssg.starroad.user.repository.UserRepository; import org.junit.jupiter.api.BeforeEach; @@ -21,6 +24,7 @@ import org.springframework.web.context.WebApplicationContext; import java.time.Instant; +import java.time.LocalDate; import java.util.Map; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -67,10 +71,21 @@ public void createAccessToken() throws Exception { // given final String url = "/api/token/access-token"; - // 테스트용 사용자를 생성하고 데이터베이스에 저장합 + // 테스트용 사용자를 생성하고 데이터베이스에 저장 User testUser = userRepository.save(User.builder() .email("user@example.com") - .password("Valid123$") + .password("Valid123$") // 비밀번호는 유효성 규칙에 맞춰 생성 + .name("John Doe") + .nickname("JD2024") // 닉네임은 유니크해야 함, 데이터베이스 상황에 따라 수정 필요 + .gender(Gender.MALE) // 성별은 ENUM 값 중 하나 + .birth(LocalDate.of(1990, 1, 1)) // LocalDate 객체로 생년월일 설정 + .phone("01012345678") // 하이픈 없는 전화번호 + .providerType(ProviderType.GOOGLE) // 인증 제공자 유형 + .providerId("123456") // 제공자 ID + .imagePath("/path/to/image.jpg") // 이미지 경로 + .reviewExp(0) // 리뷰 경험치 + .point(0) // 포인트 + .activeStatus(ActiveStatus.ACTIVE) // 활성 상태 .build()); // 테스트 사용자의 ID를 사용하여 리프레시 토큰을 생성 diff --git a/src/test/java/com/ssg/starroad/user/controller/UserControllerTest.java b/src/test/java/com/ssg/starroad/user/controller/UserControllerTest.java new file mode 100644 index 0000000..e357a50 --- /dev/null +++ b/src/test/java/com/ssg/starroad/user/controller/UserControllerTest.java @@ -0,0 +1,145 @@ +package com.ssg.starroad.user.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ssg.starroad.config.jwt.TokenProvider; +import com.ssg.starroad.user.dto.LoginRequest; +import com.ssg.starroad.user.dto.UserDTO; +import com.ssg.starroad.user.enums.ActiveStatus; +import com.ssg.starroad.user.enums.Gender; +import com.ssg.starroad.user.enums.ProviderType; +import com.ssg.starroad.user.repository.RefreshTokenRepository; +import com.ssg.starroad.user.repository.UserRepository; +import com.ssg.starroad.user.service.RefreshTokenService; +import com.ssg.starroad.user.service.UserService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +public class UserControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private UserRepository userRepository; + + @Autowired + private RefreshTokenRepository refreshTokenRepository; + + @Autowired + private UserService userService; + + @Autowired + private TokenProvider tokenProvider; + + @Autowired + private RefreshTokenService refreshTokenService; + + private ObjectMapper objectMapper = new ObjectMapper(); + private static final Logger logger = LoggerFactory.getLogger(UserControllerTest.class); + + @BeforeEach + public void setup() { +// 사용자 데이터를 삭제하기 전에 해당 사용자의 모든 리프레시 토큰을 삭제 + userRepository.findByEmail("testuser@example.com") + .ifPresent(user -> { + refreshTokenRepository.deleteByUserId(user.getId()); // 리프레시 토큰 먼저 삭제 + userRepository.delete(user); // 이후 사용자 삭제 + }); + + // 테스트용 사용자 데이터 생성 + UserDTO userDTO = new UserDTO(); + userDTO.setEmail("testuser@example.com"); + userDTO.setPassword("Valid123$!"); + userDTO.setName("TestUser"); + userDTO.setNickname("Tester"); + userDTO.setGender(Gender.MALE); + userDTO.setBirth(LocalDate.of(1990, 1, 1)); + userDTO.setPhone("01012345678"); + userDTO.setProviderType(ProviderType.GOOGLE); + userDTO.setProviderId("providerId123"); + userDTO.setImagePath("/path/to/image.jpg"); + userDTO.setReviewExp(0); + userDTO.setPoint(0); + userDTO.setActiveStatus(ActiveStatus.ACTIVE); + + userService.save(userDTO); + setupSecurityContext("testuser@example.com"); + } + private void setupSecurityContext(String email) { + UserDetails userDetails = userService.loadUserByUsername(email); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } + @Test + public void testLoadUserByUsername() { + UserDetails userDetails = userService.loadUserByUsername("testuser@example.com"); + assertNotNull(userDetails); + assertEquals("testuser@example.com", userDetails.getUsername()); + } + @Test + public void testLogin() throws Exception { + LoginRequest loginRequest = new LoginRequest("testuser@example.com", "Valid123$!"); + String loginContent = objectMapper.writeValueAsString(loginRequest); + + logger.info("Sending login request for {}", loginRequest.getEmail()); + + MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/user/login") + .contentType(MediaType.APPLICATION_JSON) + .content(loginContent)) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.accessToken").exists()) + .andExpect(jsonPath("$.refreshToken").exists()) + .andExpect(header().exists("Authorization")) + .andReturn(); + + logger.info("Login test passed for {}", loginRequest.getEmail()); + logger.info("Response: {}", result.getResponse().getContentAsString()); + } + + @Test + public void whenInvalidUser_thenUnauthorized() throws Exception { + LoginRequest loginRequest = new LoginRequest("wrong1@google.com", "password@456"); + + // 특정 이메일과 패스워드 조합에 대한 결과를 명시적으로 설정 + mockMvc.perform(post("/user/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(loginRequest))) + .andExpect(status().isUnauthorized()); + } + @AfterEach + public void clearSecurityContext() { + SecurityContextHolder.clearContext(); + } +} diff --git a/src/test/java/com/ssg/starroad/user/service/RefreshTokenServiceTest.java b/src/test/java/com/ssg/starroad/user/service/RefreshTokenServiceTest.java new file mode 100644 index 0000000..70fcb69 --- /dev/null +++ b/src/test/java/com/ssg/starroad/user/service/RefreshTokenServiceTest.java @@ -0,0 +1,4 @@ +package com.ssg.starroad.user.service; + +public class RefreshTokenServiceTest { +} diff --git a/src/test/java/com/ssg/starroad/user/service/TokenServiceTest.java b/src/test/java/com/ssg/starroad/user/service/TokenServiceTest.java new file mode 100644 index 0000000..594498f --- /dev/null +++ b/src/test/java/com/ssg/starroad/user/service/TokenServiceTest.java @@ -0,0 +1,4 @@ +package com.ssg.starroad.user.service; + +public class TokenServiceTest { +} From a98dcb6cc1e16c962632185e27f947bd59e49fb8 Mon Sep 17 00:00:00 2001 From: HRiver Date: Wed, 22 May 2024 21:19:28 +0900 Subject: [PATCH 04/49] fix application naver --- .../java/com/ssg/starroad/common/service/clova/Sentiment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ssg/starroad/common/service/clova/Sentiment.java b/src/main/java/com/ssg/starroad/common/service/clova/Sentiment.java index 26443b9..9f4b4dc 100644 --- a/src/main/java/com/ssg/starroad/common/service/clova/Sentiment.java +++ b/src/main/java/com/ssg/starroad/common/service/clova/Sentiment.java @@ -24,9 +24,9 @@ public class Sentiment { private final RestTemplate restTemplate; - @Value("${clova.client.key.id}") + @Value("${naver.openapi.client-id}") private String CLOVA_API_KEY_ID; - @Value("${clova.client.key.secret}") + @Value("${naver.openapi.client}") private String CLOVA_API_KEY; private final String clovaUrl = "https://naveropenapi.apigw.ntruss.com/sentiment-analysis/v1/analyze"; From c2cfa6fcb1b31e615697aae86067b8d05b78433c Mon Sep 17 00:00:00 2001 From: choijh9023 Date: Thu, 23 May 2024 14:11:08 +0900 Subject: [PATCH 05/49] ReviewLike : reviewlike cancle and add Task /API binding --- .../ssg/starroad/review/controller/ReviewLikeController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java b/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java index 8bf2ead..8397271 100644 --- a/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java +++ b/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java @@ -12,8 +12,8 @@ public class ReviewLikeController { private final ReviewLikeService reviewLikeService; - @PostMapping - public ResponseEntity addLike(@RequestParam Long userId, @RequestParam Long reviewId) { + @PostMapping("/{reviewId}/{userId}") + public ResponseEntity addLike(@PathVariable Long reviewId, @PathVariable Long userId) { reviewLikeService.addLike(userId, reviewId); return ResponseEntity.ok().build(); } From fd51bc1b3675a39fdbfcf76e9855047f7609ce8f Mon Sep 17 00:00:00 2001 From: HRiver Date: Thu, 23 May 2024 16:34:44 +0900 Subject: [PATCH 06/49] reward-process: fix coupon-history --- .../coupon/controller/CouponController.java | 8 ++++---- .../ssg/starroad/coupon/entity/CouponHistory.java | 1 + .../impl/CouponHistoryRepositoryCustom.java | 2 +- .../coupon/service/CouponHistoryService.java | 2 +- .../service/impl/CouponHistoryServiceImpl.java | 13 ++++++++++--- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/ssg/starroad/coupon/controller/CouponController.java b/src/main/java/com/ssg/starroad/coupon/controller/CouponController.java index a8b39fd..008ac3c 100644 --- a/src/main/java/com/ssg/starroad/coupon/controller/CouponController.java +++ b/src/main/java/com/ssg/starroad/coupon/controller/CouponController.java @@ -17,15 +17,15 @@ public class CouponController { private final CouponHistoryService couponHistoryService; @PostMapping("/issue") - public ResponseEntity issueCoupon(@RequestBody IssueCouponRequest request) { + public ResponseEntity issueCoupon(@RequestBody IssueCouponRequest request) { if (request.getUserId() == null || request.getCouponId() == null) { - return ResponseEntity.badRequest().body("UserId or CouponId cannot be null"); + return ResponseEntity.badRequest().body(0); } - couponHistoryService.CouponUserAdd(request.getUserId(), request.getCouponId()); + int rate = couponHistoryService.CouponUserAdd(request.getUserId(), request.getCouponId()); - return ResponseEntity.ok("Coupon issued successfully to user ID " + request.getUserId()); + return ResponseEntity.ok(rate); } @GetMapping("/{userId}/coupon/list") public ResponseEntity> getUserCoupons(@PathVariable Long userId) { diff --git a/src/main/java/com/ssg/starroad/coupon/entity/CouponHistory.java b/src/main/java/com/ssg/starroad/coupon/entity/CouponHistory.java index 2b5f9eb..b0d95dd 100644 --- a/src/main/java/com/ssg/starroad/coupon/entity/CouponHistory.java +++ b/src/main/java/com/ssg/starroad/coupon/entity/CouponHistory.java @@ -29,6 +29,7 @@ public class CouponHistory extends BaseTimeEntity { private Long couponId; private boolean usageStatus; private LocalDate expiredAt; + private int discountRate; public void useCoupon(){ this.usageStatus=true; diff --git a/src/main/java/com/ssg/starroad/coupon/repository/impl/CouponHistoryRepositoryCustom.java b/src/main/java/com/ssg/starroad/coupon/repository/impl/CouponHistoryRepositoryCustom.java index 6106fce..eaf931f 100644 --- a/src/main/java/com/ssg/starroad/coupon/repository/impl/CouponHistoryRepositoryCustom.java +++ b/src/main/java/com/ssg/starroad/coupon/repository/impl/CouponHistoryRepositoryCustom.java @@ -37,7 +37,7 @@ public List findCouponsByUserId(Long userId) { qCouponHistory.id.as("couponHistoryId"), qCoupon.id.as("couponId"), qCoupon.name.as("couponName"), - qCoupon.discountRate.as("couponDiscountRate"), + qCouponHistory.discountRate.as("couponDiscountRate"), qCoupon.discountAmount.as("couponDiscountAmount"), qCoupon.status.as("couponStatus"), qCouponHistory.usageStatus.as("couponUsageStatus"), diff --git a/src/main/java/com/ssg/starroad/coupon/service/CouponHistoryService.java b/src/main/java/com/ssg/starroad/coupon/service/CouponHistoryService.java index b5ac3b8..558566b 100644 --- a/src/main/java/com/ssg/starroad/coupon/service/CouponHistoryService.java +++ b/src/main/java/com/ssg/starroad/coupon/service/CouponHistoryService.java @@ -7,7 +7,7 @@ public interface CouponHistoryService { - void CouponUserAdd(Long userID, Long couponID); + int CouponUserAdd(Long userID, Long couponID); diff --git a/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java b/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java index 19ffee3..a3118f0 100644 --- a/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java +++ b/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java @@ -1,6 +1,7 @@ package com.ssg.starroad.coupon.service.impl; import com.ssg.starroad.coupon.DTO.CouponDTO; +import com.ssg.starroad.coupon.entity.Coupon; import com.ssg.starroad.coupon.entity.CouponHistory; import com.ssg.starroad.coupon.repository.CouponHistoryRepository; import com.ssg.starroad.coupon.repository.CouponRepository; @@ -17,6 +18,7 @@ import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; @Service @@ -30,16 +32,21 @@ public class CouponHistoryServiceImpl implements CouponHistoryService { private final RewardProcessRepository rewardProcessRepository; @Override - public void CouponUserAdd(Long userID, Long couponID) { + public int CouponUserAdd(Long userID, Long couponID) { + Coupon coupon = couponRepository.findById(couponID).orElseThrow(); + int max = coupon.getMaxAmount(); + int min = coupon.getMinAmount(); + int rate = ThreadLocalRandom.current().nextInt(min, max + 1); + CouponHistory couponHistory = new CouponHistory(null, userRepository.findById(userID).orElseThrow(), - couponID,false, LocalDate.now().plusDays(7)); + couponID,false, LocalDate.now().plusDays(7),rate); couponHistoryRepository.save(couponHistory); RewardProcess rewardProcess =rewardProcessRepository.findById(userID).orElseThrow(); rewardProcess.setIssueStatus(true); rewardProcessRepository.save(rewardProcess); - + return rate; } @Override From 48f72098184e5ee62fc83e65afb072b5a1ef23af Mon Sep 17 00:00:00 2001 From: HRiver Date: Thu, 23 May 2024 17:08:48 +0900 Subject: [PATCH 07/49] reward-process: fix reward every monday --- .../reward/service/impl/RewardProcessServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java b/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java index caf4c4a..4319158 100644 --- a/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java +++ b/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java @@ -9,7 +9,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.DayOfWeek; import java.time.LocalDate; +import java.time.temporal.TemporalAdjusters; import java.util.Objects; import java.util.Optional; @@ -27,7 +29,7 @@ public void startRewardProcess(Long userId) { .orElse(new RewardProcess(userId)); // 존재하지 않는 경우 새 객체 생성 rewardProcess.setRewardStatus(false); // 기존 로직대로 상태 설정 - rewardProcess.setExpiredAt(LocalDate.now().plusDays(7)); + rewardProcess.setExpiredAt(LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY))); rewardProcess.setReviewCount(0); rewardProcess.setCouponCount(0); rewardProcessRepository.save(rewardProcess); // 저장 From c06570b4ea96bd383726028f8611a5938e9b7766 Mon Sep 17 00:00:00 2001 From: HRiver Date: Thu, 23 May 2024 17:16:12 +0900 Subject: [PATCH 08/49] reward-process: fix reward every monday --- .../coupon/service/impl/CouponHistoryServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java b/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java index a3118f0..3f1b74c 100644 --- a/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java +++ b/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java @@ -14,7 +14,9 @@ import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; +import java.time.DayOfWeek; import java.time.LocalDate; +import java.time.temporal.TemporalAdjusters; import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -40,7 +42,7 @@ public int CouponUserAdd(Long userID, Long couponID) { CouponHistory couponHistory = new CouponHistory(null, userRepository.findById(userID).orElseThrow(), - couponID,false, LocalDate.now().plusDays(7),rate); + couponID,false, LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY)),rate); couponHistoryRepository.save(couponHistory); RewardProcess rewardProcess =rewardProcessRepository.findById(userID).orElseThrow(); From 715cc5224e78ebbd32956b38acff98f8d9129033 Mon Sep 17 00:00:00 2001 From: choijh9023 Date: Fri, 24 May 2024 16:34:38 +0900 Subject: [PATCH 09/49] #84 Like method : finish Like method --- .../ssg/starroad/review/DTO/ReviewDTO.java | 1 + .../starroad/review/DTO/ReviewLikeDTO.java | 14 ++++++++ .../controller/ReviewLikeController.java | 25 ++++++++++--- .../repository/ReviewLikeRepository.java | 8 ++++- .../review/service/ReviewLikeService.java | 7 +++- .../service/impl/ReviewLikeServiceImpl.java | 24 +++++++++++-- .../starroad/shop/DTO/StoreWithReviewDTO.java | 4 ++- .../shop/controller/StoreController.java | 7 ++-- .../starroad/shop/service/StoreService.java | 2 +- .../shop/service/impl/StoreServiceImpl.java | 35 +++++++++++-------- .../user/repository/UserRepository.java | 1 + .../repository/ReviewLikeRepositoryTest.java | 20 +++++++++++ .../impl/ReviewLikeServiceImplTest.java | 30 ++++++++++++++++ .../shop/controller/StoreControllerTest.java | 2 +- .../service/impl/StoreServiceImplTest.java | 2 +- 15 files changed, 151 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/ssg/starroad/review/DTO/ReviewLikeDTO.java create mode 100644 src/test/java/com/ssg/starroad/review/repository/ReviewLikeRepositoryTest.java create mode 100644 src/test/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImplTest.java diff --git a/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java b/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java index 146793d..1b840a5 100644 --- a/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java +++ b/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java @@ -33,6 +33,7 @@ public class ReviewDTO { private List reviewImages; // 리뷰 이미지 리스트 추가 private List reviewFeedbacks; // 리뷰 피드백 추가 private Long reviewcount; // 리뷰 갯수 + private boolean isLiked; // 내가 추가한 필드 choims 24/05/23 private LocalDateTime createDate; @Enumerated(EnumType.STRING) private ConfidenceType confidence; diff --git a/src/main/java/com/ssg/starroad/review/DTO/ReviewLikeDTO.java b/src/main/java/com/ssg/starroad/review/DTO/ReviewLikeDTO.java new file mode 100644 index 0000000..ad893cb --- /dev/null +++ b/src/main/java/com/ssg/starroad/review/DTO/ReviewLikeDTO.java @@ -0,0 +1,14 @@ +package com.ssg.starroad.review.DTO; + +import lombok.*; + +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Data +public class ReviewLikeDTO { + private boolean isLiked; + private long likeCount; + +} diff --git a/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java b/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java index 8397271..6212f7b 100644 --- a/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java +++ b/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java @@ -1,20 +1,35 @@ package com.ssg.starroad.review.controller; +import com.ssg.starroad.review.DTO.ReviewLikeDTO; import com.ssg.starroad.review.service.ReviewLikeService; +import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/review-likes") @RequiredArgsConstructor public class ReviewLikeController { - private final ReviewLikeService reviewLikeService; + private final UserRepository userRepository; + + @GetMapping("/{userEmail}") + public ResponseEntity> getUserLikes(@PathVariable String userEmail) { + User user = userRepository.findByEmail(userEmail) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + List likedReviewIds = reviewLikeService.getLikedReviewIdsByUserId(user.getId()); + return ResponseEntity.ok(likedReviewIds); + } - @PostMapping("/{reviewId}/{userId}") - public ResponseEntity addLike(@PathVariable Long reviewId, @PathVariable Long userId) { - reviewLikeService.addLike(userId, reviewId); - return ResponseEntity.ok().build(); + @PostMapping("/{reviewId}/{userEmail}") + public ResponseEntity addLike(@PathVariable Long reviewId, @PathVariable String userEmail) { + User user = userRepository.findByEmail(userEmail) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + ReviewLikeDTO response = reviewLikeService.addLike(userEmail, reviewId); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/ssg/starroad/review/repository/ReviewLikeRepository.java b/src/main/java/com/ssg/starroad/review/repository/ReviewLikeRepository.java index 440afb6..2383fa8 100644 --- a/src/main/java/com/ssg/starroad/review/repository/ReviewLikeRepository.java +++ b/src/main/java/com/ssg/starroad/review/repository/ReviewLikeRepository.java @@ -4,11 +4,17 @@ import com.ssg.starroad.review.entity.ReviewLike; import com.ssg.starroad.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; import java.util.Optional; public interface ReviewLikeRepository extends JpaRepository { Optional findByUserIdAndReviewId(Long userId, Long reviewId); void deleteByUserIdAndReviewId(Long userId, Long reviewId); - + @Query("SELECT rl.review.id FROM ReviewLike rl WHERE rl.user.id = :userId") + List findLikedReviewIdsByUserId(@Param("userId")Long userId); boolean existsByUserAndReview(User user, Review review); + boolean existsByUser_IdAndReview_Id(Long userId, Long reviewId); } diff --git a/src/main/java/com/ssg/starroad/review/service/ReviewLikeService.java b/src/main/java/com/ssg/starroad/review/service/ReviewLikeService.java index c9900d9..6bd28bb 100644 --- a/src/main/java/com/ssg/starroad/review/service/ReviewLikeService.java +++ b/src/main/java/com/ssg/starroad/review/service/ReviewLikeService.java @@ -1,7 +1,12 @@ package com.ssg.starroad.review.service; +import com.ssg.starroad.review.DTO.ReviewLikeDTO; + +import java.util.List; + public interface ReviewLikeService { - void addLike(Long userId, Long reviewId); + ReviewLikeDTO addLike(String userEmail, Long reviewId); void removeLike(Long userId, Long reviewId); boolean existsByUserIdAndReviewId(Long userId, Long reviewId); + List getLikedReviewIdsByUserId(Long userId); } \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java index d99de0e..2e778b8 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java @@ -1,5 +1,6 @@ package com.ssg.starroad.review.service.impl; +import com.ssg.starroad.review.DTO.ReviewLikeDTO; import com.ssg.starroad.review.entity.Review; import com.ssg.starroad.review.entity.ReviewLike; import com.ssg.starroad.review.repository.ReviewLikeRepository; @@ -11,9 +12,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; import java.util.Optional; - @Service @RequiredArgsConstructor public class ReviewLikeServiceImpl implements ReviewLikeService { @@ -22,18 +23,27 @@ public class ReviewLikeServiceImpl implements ReviewLikeService { private final UserRepository userRepository; private final ReviewRepository reviewRepository; + @Override + public List getLikedReviewIdsByUserId(Long userId) { + return reviewLikeRepository.findLikedReviewIdsByUserId(userId); + } + @Override @Transactional - public void addLike(Long userId, Long reviewId) { - User user = userRepository.findById(userId) + public ReviewLikeDTO addLike(String userEmail, Long reviewId) { + User user = userRepository.findByEmail(userEmail) .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + Long userId = user.getId(); Review review = reviewRepository.findById(reviewId) .orElseThrow(() -> new IllegalArgumentException("리뷰를 찾을 수 없습니다.")); Optional reviewLikeOptional = reviewLikeRepository.findByUserIdAndReviewId(userId, reviewId); + boolean isLiked; if (reviewLikeOptional.isPresent()) { // 이미 좋아요가 존재하면 좋아요 취소 (삭제) 로직 reviewLikeRepository.delete(reviewLikeOptional.get()); + review.setLikeCount(review.getLikeCount() - 1); + isLiked = false; } else { // 좋아요가 존재하지 않으면 새로운 좋아요 추가 ReviewLike reviewLike = ReviewLike.builder() @@ -41,7 +51,15 @@ public void addLike(Long userId, Long reviewId) { .review(review) .build(); reviewLikeRepository.save(reviewLike); + review.setLikeCount(review.getLikeCount() + 1); + isLiked = true; } + reviewRepository.save(review); // 변경된 likeCount를 저장 + + return ReviewLikeDTO.builder() + .isLiked(isLiked) + .likeCount(review.getLikeCount()) + .build(); } @Override diff --git a/src/main/java/com/ssg/starroad/shop/DTO/StoreWithReviewDTO.java b/src/main/java/com/ssg/starroad/shop/DTO/StoreWithReviewDTO.java index 11fba61..0c09bbf 100644 --- a/src/main/java/com/ssg/starroad/shop/DTO/StoreWithReviewDTO.java +++ b/src/main/java/com/ssg/starroad/shop/DTO/StoreWithReviewDTO.java @@ -35,7 +35,7 @@ public class StoreWithReviewDTO { private String imagePath; - + private Long userId; // 로그인을 한 유저에 대한 필드 private String storeType; //프로그래스바 관련필드 private Long revisitCount; @@ -46,4 +46,6 @@ public class StoreWithReviewDTO { private int pageNumber; // 현재 페이지 번호 private int pageSize; // 페이지 크기 private boolean hasNext; // 다음 페이지 존재 여부 + + } diff --git a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java index 192343f..165fbef 100644 --- a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java +++ b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java @@ -32,15 +32,18 @@ public ResponseEntity> StoreSearch() { @GetMapping("/{id}/reviews") public ResponseEntity StoreGetWithReviewById( @PathVariable Long id, + @RequestParam String userEmail, // userId 파라미터 추 @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { try { - StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, page, size); + String test = "hklee@example.com"; + StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, test, page, size); return ResponseEntity.ok(storeWithReviewDTO); } catch (RuntimeException e) { return ResponseEntity.notFound().build(); } } + // 특정 매장의 가이드 맵을 조회하는 요청을 처리하는 메소드입니다. @GetMapping("/{id}/guidemap") public ResponseEntity StoreGetGuidemapById(@PathVariable Long id) { @@ -49,8 +52,6 @@ public ResponseEntity StoreGetGuidemapById(@PathVariable Long id) { // 매장 서비스를 통해 특정 매장의 가이드 맵을 조회합니다. StoreDTO storeDTO = storeService.findStore(id); // 조회된 가이드 맵을 응답으로 반환합니다. - - log.info(storeDTO.toString()); return ResponseEntity.ok(storeDTO); } catch (RuntimeException e) { diff --git a/src/main/java/com/ssg/starroad/shop/service/StoreService.java b/src/main/java/com/ssg/starroad/shop/service/StoreService.java index 55a462b..796b060 100644 --- a/src/main/java/com/ssg/starroad/shop/service/StoreService.java +++ b/src/main/java/com/ssg/starroad/shop/service/StoreService.java @@ -10,6 +10,6 @@ public interface StoreService { List searchStoreList(Long id); - StoreWithReviewDTO findStoreWithReview(Long id, int pageNo,int pageSize); + StoreWithReviewDTO findStoreWithReview(Long storeId,String userEmail, int pageNo,int pageSize); StoreDTO findStore(Long id); } diff --git a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java index 6cd65dd..af1e17d 100644 --- a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java +++ b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java @@ -5,6 +5,7 @@ import com.ssg.starroad.review.DTO.ReviewImageDTO; import com.ssg.starroad.review.entity.Review; import com.ssg.starroad.review.repository.ReviewImageRepository; +import com.ssg.starroad.review.repository.ReviewLikeRepository; import com.ssg.starroad.review.repository.ReviewRepository; import com.ssg.starroad.review.service.ReviewFeedbackService; import com.ssg.starroad.review.service.ReviewImageService; @@ -14,6 +15,7 @@ import com.ssg.starroad.shop.entity.Store; import com.ssg.starroad.shop.repository.StoreRepository; import com.ssg.starroad.shop.service.StoreService; +import com.ssg.starroad.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -33,7 +35,8 @@ public class StoreServiceImpl implements StoreService { private final ReviewImageService reviewImageService; // ReviewImageService 인젝션 private final ReviewFeedbackService reviewFeedbackService; private final ReviewService reviewService; - + private final ReviewLikeRepository reviewLikeRepository; + private final UserRepository userRepository; @Override public List searchStoreList(Long id) { // 주어진 complexShoppingmallId로 매장 목록을 조회합니다. @@ -44,29 +47,31 @@ public List searchStoreList(Long id) { .map(StoreDTO::toDTO).collect(Collectors.toList()); return storeDTOList; - }@Override - public StoreWithReviewDTO findStoreWithReview(Long id, int pageNo, int pageSize) { - Store store = storeRepository.findById(id).orElseThrow(() -> new RuntimeException("존재하지 않는 스토어입니다.")); + } + @Override + public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize) { + Store store = storeRepository.findById(storeId).orElseThrow(() -> new RuntimeException("존재하지 않는 스토어입니다.")); - // 최신순으로 정렬된 Pageable 객체 생성 Pageable pageable = PageRequest.of(pageNo, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")); + Page reviewPage = reviewRepository.findAllWithPageByStoreId(storeId, pageable); - // 정렬된 Pageable 객체를 사용하여 리뷰 페이지 가져오기 - Page reviewPage = reviewRepository.findAllWithPageByStoreId(id, pageable); + Long totalReviewCount = reviewRepository.countByStoreId(storeId); + long revisitCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "재방문 하고 싶어요"); + long serviceSatisfactionCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "서비스가 마음에 들어요"); + long reasonablePriceCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "가격이 합리적입니다"); + long cleanlinessCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "매장이 청결합니다"); - Long totalReviewCount = reviewRepository.countByStoreId(id); - - // 각 선택지에 대한 피드백 개수를 조회합니다. - long revisitCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(id, "재방문 하고 싶어요"); - long serviceSatisfactionCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(id, "서비스가 마음에 들어요"); - long reasonablePriceCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(id, "가격이 합리적입니다"); - long cleanlinessCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(id, "매장이 청결합니다"); + // 사용자 이메일을 통해 좋아요 상태 확인 + Long userId = userRepository.findByEmail(userEmail).orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")).getId(); +// List likedReviewIds = reviewLikeRepository.findLikedReviewIdsByUserId(userId); List reviewDTOList = reviewPage.stream() .map(review -> { List reviewImageDTOs = reviewImageService.getReviewImages(review.getId()); List reviewFeedbackDTOs = reviewFeedbackService.getReviewFeedback(review.getId()); Long userReviewCount = reviewService.countReviewsByUserId(review.getUser().getId()); +// boolean isLiked = likedReviewIds.contains(review.getId()); + boolean isLiked = reviewLikeRepository.existsByUser_IdAndReview_Id(userId, review.getId()); return ReviewDTO.builder() .id(review.getId()) @@ -83,6 +88,7 @@ public StoreWithReviewDTO findStoreWithReview(Long id, int pageNo, int pageSize) .reviewcount(userReviewCount) .reviewImages(reviewImageDTOs) .reviewFeedbacks(reviewFeedbackDTOs) + .isLiked(isLiked) // 로그인한 사용자가 좋아요를 눌렀는지 여부 추가 .build(); }) .collect(Collectors.toList()); @@ -111,6 +117,7 @@ public StoreWithReviewDTO findStoreWithReview(Long id, int pageNo, int pageSize) + @Override public StoreDTO findStore(Long id) { Store store = storeRepository.findById(id).orElseThrow(() -> new RuntimeException("존재하지 않은 매장입니다")); diff --git a/src/main/java/com/ssg/starroad/user/repository/UserRepository.java b/src/main/java/com/ssg/starroad/user/repository/UserRepository.java index 211563c..dc1e507 100644 --- a/src/main/java/com/ssg/starroad/user/repository/UserRepository.java +++ b/src/main/java/com/ssg/starroad/user/repository/UserRepository.java @@ -8,5 +8,6 @@ public interface UserRepository extends JpaRepository { Optional findByid(Long id); + Optional findByEmail(String email); } diff --git a/src/test/java/com/ssg/starroad/review/repository/ReviewLikeRepositoryTest.java b/src/test/java/com/ssg/starroad/review/repository/ReviewLikeRepositoryTest.java new file mode 100644 index 0000000..da38167 --- /dev/null +++ b/src/test/java/com/ssg/starroad/review/repository/ReviewLikeRepositoryTest.java @@ -0,0 +1,20 @@ +package com.ssg.starroad.review.repository; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +@SpringBootTest +class ReviewLikeRepositoryTest { + + + @Autowired + private ReviewLikeRepository reviewLikeRepository; + + @Test + void booleanteest() { + assertThat(reviewLikeRepository.existsByUser_IdAndReview_Id(1L,43L)).isTrue(); + } +} \ No newline at end of file diff --git a/src/test/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImplTest.java b/src/test/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImplTest.java new file mode 100644 index 0000000..52caddb --- /dev/null +++ b/src/test/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImplTest.java @@ -0,0 +1,30 @@ +package com.ssg.starroad.review.service.impl; + +import com.ssg.starroad.review.service.ReviewImageService; +import com.ssg.starroad.review.service.ReviewLikeService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class ReviewLikeServiceImplTest { + + + @Autowired + private ReviewLikeService reviewLikeService; + + private static final Logger log = LogManager.getLogger(ReviewLikeService.class); + @Test + @DisplayName("좋아요 누른 리뷰 체크하는테스트") + public void test(){ + log.info("좋아요테스트"); + System.out.println("좋아요테스트"); + log.info( reviewLikeService.getLikedReviewIdsByUserId(1L)); + log.info("!!!"); + } +} \ No newline at end of file diff --git a/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java b/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java index df5106d..7a12360 100644 --- a/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java +++ b/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java @@ -25,7 +25,7 @@ public void teststoreController() { @Test public void teststoreController2() { log.info( - storeController.StoreGetWithReviewById(3L,1,20)); + storeController.StoreGetWithReviewById(3L,"hklee@example.com",1,20)); } @Test diff --git a/src/test/java/com/ssg/starroad/shop/service/impl/StoreServiceImplTest.java b/src/test/java/com/ssg/starroad/shop/service/impl/StoreServiceImplTest.java index c508a11..873b67f 100644 --- a/src/test/java/com/ssg/starroad/shop/service/impl/StoreServiceImplTest.java +++ b/src/test/java/com/ssg/starroad/shop/service/impl/StoreServiceImplTest.java @@ -42,7 +42,7 @@ void getStore() { @Test @DisplayName("Store단일 매장 전체 리뷰 작성메소드 테스트") void StoreWithAllReviewTest() { - log.info(storeService.findStoreWithReview(2L,1,20)); + log.info(storeService.findStoreWithReview(2L,"hklee@example.com",1,20)); } From 83d7cb6b270a46af119edb8a658fd05482e84c81 Mon Sep 17 00:00:00 2001 From: mariasoo <9723maria@gmail.com> Date: Fri, 24 May 2024 16:57:28 +0900 Subject: [PATCH 10/49] login : make security --- build.gradle | 8 +- .../java/com/ssg/starroad/config/Bcrypt.java | 14 ++ .../com/ssg/starroad/config/CorsConfig.java | 2 +- .../com/ssg/starroad/config/EmailConfig.java | 64 +++++++ .../config/TokenAuthenticationFilter.java | 14 ++ .../starroad/config/WebSecurityConfig.java | 19 +- .../starroad/config/jwt/TokenProvider.java | 7 + .../config/oauth/OAuth2SuccessHandler.java | 8 + .../user/controller/AuthController.java | 44 +++++ .../user/controller/OAuth2Controller.java | 47 +++-- .../user/controller/TokenApiController.java | 1 - .../user/controller/UserController.java | 125 ++++++++++-- .../ssg/starroad/user/dto/LoginResponse.java | 7 +- .../ssg/starroad/user/dto/LogoutRequest.java | 11 ++ .../starroad/user/dto/OAuth2UserInfoDTO.java | 3 +- .../user/dto/PasswordUpdateRequest.java | 16 ++ .../com/ssg/starroad/user/dto/UserDTO.java | 18 ++ .../ssg/starroad/user/entity/AuthCode.java | 29 +++ .../starroad/user/entity/RefreshToken.java | 2 + .../com/ssg/starroad/user/entity/User.java | 34 +++- .../ssg/starroad/user/enums/ActiveStatus.java | 5 +- .../ssg/starroad/user/enums/ProviderType.java | 2 +- .../user/repository/AuthCodeRepository.java | 7 + .../repository/RefreshTokenRepository.java | 4 + .../user/repository/UserRepository.java | 1 + .../starroad/user/service/AuthService.java | 8 + .../user/service/CustomOAuth2UserService.java | 6 +- .../starroad/user/service/EmailService.java | 8 + .../starroad/user/service/UserService.java | 12 ++ .../user/service/impl/AuthServiceImpl.java | 45 +++++ .../impl/CustomOAuth2UserServiceImpl.java | 178 +++++++++++++----- .../user/service/impl/EmailServiceImpl.java | 30 +++ .../service/impl/RefreshTokenServiceImpl.java | 23 ++- .../user/service/impl/UserServiceImpl.java | 91 +++++++-- .../user/controller/UserControllerTest.java | 2 - 35 files changed, 771 insertions(+), 124 deletions(-) create mode 100644 src/main/java/com/ssg/starroad/config/Bcrypt.java create mode 100644 src/main/java/com/ssg/starroad/config/EmailConfig.java create mode 100644 src/main/java/com/ssg/starroad/user/controller/AuthController.java create mode 100644 src/main/java/com/ssg/starroad/user/dto/LogoutRequest.java create mode 100644 src/main/java/com/ssg/starroad/user/dto/PasswordUpdateRequest.java create mode 100644 src/main/java/com/ssg/starroad/user/entity/AuthCode.java create mode 100644 src/main/java/com/ssg/starroad/user/repository/AuthCodeRepository.java create mode 100644 src/main/java/com/ssg/starroad/user/service/AuthService.java create mode 100644 src/main/java/com/ssg/starroad/user/service/EmailService.java create mode 100644 src/main/java/com/ssg/starroad/user/service/impl/AuthServiceImpl.java create mode 100644 src/main/java/com/ssg/starroad/user/service/impl/EmailServiceImpl.java diff --git a/build.gradle b/build.gradle index dfaf554..54c282d 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,6 @@ configurations { compileOnly { extendsFrom annotationProcessor } - all*.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' } repositories { @@ -28,6 +27,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-mail:3.0.5' + implementation 'com.sun.mail:jakarta.mail:2.0.1' // 버전을 2.0.1로 변경 // LOMBOK compileOnly 'org.projectlombok:lombok' @@ -59,11 +60,6 @@ dependencies { testImplementation 'com.h2database:h2' testImplementation 'org.springframework.boot:spring-boot-starter-test' - // TEST LOG4J2 - testImplementation 'org.springframework.boot:spring-boot-starter-log4j2' - testImplementation 'org.apache.logging.log4j:log4j-slf4j-impl' - testImplementation 'org.apache.logging.log4j:log4j-api' - // TEST JUNIT testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' diff --git a/src/main/java/com/ssg/starroad/config/Bcrypt.java b/src/main/java/com/ssg/starroad/config/Bcrypt.java new file mode 100644 index 0000000..e254099 --- /dev/null +++ b/src/main/java/com/ssg/starroad/config/Bcrypt.java @@ -0,0 +1,14 @@ +package com.ssg.starroad.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class Bcrypt +{ + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/ssg/starroad/config/CorsConfig.java b/src/main/java/com/ssg/starroad/config/CorsConfig.java index 9db5d27..ba85e82 100644 --- a/src/main/java/com/ssg/starroad/config/CorsConfig.java +++ b/src/main/java/com/ssg/starroad/config/CorsConfig.java @@ -13,7 +13,7 @@ public UrlBasedCorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.addAllowedOriginPattern("http://localhost:8080"); + config.addAllowedOriginPattern("http://localhost:8080"); // Vue 애플리케이션 주소 추가 config.addAllowedHeader("*"); // 서버가 받을 수 있는 모든 헤더를 정의 // 각 HTTP 메소드를 개별적으로 추가 diff --git a/src/main/java/com/ssg/starroad/config/EmailConfig.java b/src/main/java/com/ssg/starroad/config/EmailConfig.java new file mode 100644 index 0000000..a910502 --- /dev/null +++ b/src/main/java/com/ssg/starroad/config/EmailConfig.java @@ -0,0 +1,64 @@ +package com.ssg.starroad.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; +// JavaMailSender 인터페이스를 구현 +// JavaMail API를 사용하여 이메일을 전송하는 데 사용 + +@Configuration +public class EmailConfig { + + @Value("${spring.mail.host}") + private String host; + + @Value("${spring.mail.port}") + private int port; + + @Value("${spring.mail.username}") + private String username; + + @Value("${spring.mail.password}") + private String password; + + @Value("${spring.mail.properties.mail.smtp.auth}") + private boolean auth; + + @Value("${spring.mail.properties.mail.smtp.starttls.enable}") + private boolean starttlsEnable; + + @Value("${spring.mail.properties.mail.smtp.starttls.required}") + private boolean starttlsRequired; + + @Value("${spring.mail.properties.mail.smtp.connectiontimeout}") + private int connectionTimeout; + + @Value("${spring.mail.properties.mail.smtp.timeout}") + private int timeout; + + @Value("${spring.mail.properties.mail.smtp.writetimeout}") + private int writeTimeout; + + @Bean + public JavaMailSender javaMailSender() { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(host); + mailSender.setPort(port); + mailSender.setUsername(username); + mailSender.setPassword(password); + + Properties props = mailSender.getJavaMailProperties(); + props.put("mail.smtp.auth", auth); + props.put("mail.smtp.starttls.enable", starttlsEnable); + props.put("mail.smtp.starttls.required", starttlsRequired); + props.put("mail.smtp.connectiontimeout", connectionTimeout); + props.put("mail.smtp.timeout", timeout); + props.put("mail.smtp.writetimeout", writeTimeout); + + return mailSender; + } +} diff --git a/src/main/java/com/ssg/starroad/config/TokenAuthenticationFilter.java b/src/main/java/com/ssg/starroad/config/TokenAuthenticationFilter.java index decddfa..75c472f 100644 --- a/src/main/java/com/ssg/starroad/config/TokenAuthenticationFilter.java +++ b/src/main/java/com/ssg/starroad/config/TokenAuthenticationFilter.java @@ -1,6 +1,7 @@ package com.ssg.starroad.config; import com.ssg.starroad.config.jwt.TokenProvider; +import com.ssg.starroad.user.entity.User; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -10,6 +11,7 @@ import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @@ -56,6 +58,18 @@ protected void doFilterInternal( // 3. 유효한 토큰인 경우, 사용자 인증 객체를 생성하고 SecurityContextHolder에 등록 if (tokenProvider.validToken(token)) { Authentication authentication = tokenProvider.getAuthentication(token); + + // 사용자 상태 확인 + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + if (userDetails instanceof User) { + User user = (User) userDetails; + if (!user.isEnabled()) { + log.info("User is not enabled: {}", user.getUsername()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + } + SecurityContextHolder.getContext().setAuthentication(authentication); } else { log.info("JWT가 유효하지 않음: {}", token); diff --git a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java index b595c66..1d75efe 100644 --- a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java +++ b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java @@ -21,11 +21,11 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + + import java.io.IOException; @Configuration @@ -35,6 +35,7 @@ public class WebSecurityConfig{ private final TokenProvider tokenProvider; private final UserService userService; + private final Bcrypt passwordEncoder; private final RefreshTokenRepository refreshTokenRepository; private final CustomOAuth2UserService customOAuth2UserService; private final OAuth2SuccessHandler oAuth2SuccessHandler; @@ -45,7 +46,7 @@ public class WebSecurityConfig{ public WebSecurityCustomizer webSecurityCustomizer() { // security를 적용하지 않을 리소스 return web -> web.ignoring() // error endpoint를 열어줌 - .requestMatchers("/error","/favicon.ico","/user/login"); + .requestMatchers("/error","/favicon.ico"); } @Bean @@ -58,8 +59,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .logout(AbstractHttpConfigurer::disable) .sessionManagement(sessionManagementConfig -> sessionManagementConfig .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeRequests(auth -> auth - .requestMatchers("/user/login", "/user/join", "/oauth2/**", "/api/token").permitAll() + .authorizeHttpRequests(auth -> auth + .requestMatchers("/user/login","/user/logout","/user/join/**","/oauth2/**","/api/token/**","/auth/**").permitAll() + .requestMatchers("/user/update-profile","/user/update-password","/user/details","/user/inactive").authenticated() .anyRequest().authenticated()) .oauth2Login(oauth -> oauth .loginPage("/user/login") @@ -80,11 +82,6 @@ private void accessDeniedHandler(HttpServletRequest request, HttpServletResponse response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"); } - @Bean - public BCryptPasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - @Bean public OAuth2SuccessHandler oAuth2SuccessHandler() { return new OAuth2SuccessHandler(tokenProvider, customOAuth2UserService); @@ -105,7 +102,7 @@ public AuthenticationManager authManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); authenticationManagerBuilder .userDetailsService(userService) - .passwordEncoder(passwordEncoder()); + .passwordEncoder(passwordEncoder.passwordEncoder()); return authenticationManagerBuilder.build(); } } \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java index 034fd1f..d97d259 100644 --- a/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java +++ b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java @@ -36,6 +36,7 @@ public String generateToken(User user, Duration expiredAt) { // 실제 jwt 토큰 생성 사용자 정보와 만료 시간을 받아 구성, 서명해 반환 public String createToken(Date expiry, User user) { + Date now = new Date(); SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtProperties.getSecretKey())); @@ -97,4 +98,10 @@ private Claims getClaims(String token) { .parseClaimsJws(token) .getBody(); } + + public String generateAccessToken(User user) { + // 액세스 토큰의 유효 기간을 설정합니다 (예: 1시간) + Duration accessTokenValidity = Duration.ofHours(1); + return generateToken(user, accessTokenValidity); + } } diff --git a/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java b/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java index 55d17a4..eb60e83 100644 --- a/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java +++ b/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java @@ -39,6 +39,14 @@ public OAuth2SuccessHandler(TokenProvider tokenProvider, CustomOAuth2UserService this.customOAuth2UserService = customOAuth2UserService; } + public static int getRefreshTokenExpiry() { + return REFRESH_TOKEN_EXPIRY; + } + + public static int getAccessTokenExpiry() { + return ACCESS_TOKEN_EXPIRY; + } + // OAuth2 인증 과정이 성공적으로 완료된 후 실행되는 메소드 // 1) 사용자 인증 정보를 활용하여 토큰을 생성하고, diff --git a/src/main/java/com/ssg/starroad/user/controller/AuthController.java b/src/main/java/com/ssg/starroad/user/controller/AuthController.java new file mode 100644 index 0000000..ef51f48 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/controller/AuthController.java @@ -0,0 +1,44 @@ +package com.ssg.starroad.user.controller; + +import com.ssg.starroad.user.service.AuthService; +import jakarta.mail.MessagingException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/auth") +public class AuthController { + + private final AuthService authService; + + @Autowired + public AuthController(AuthService authService) { + this.authService = authService; + } + + @PostMapping("/send-code") + public ResponseEntity sendAuthCode(@RequestBody Map payload) { + String email = payload.get("email"); + try { + authService.sendAuthCode(email); + return ResponseEntity.ok("인증 코드가 전송되었습니다."); + } catch (MessagingException e) { + return ResponseEntity.status(500).body("이메일 전송에 실패했습니다."); + } + } + + @PostMapping("/verify-code") + public ResponseEntity verifyAuthCode(@RequestBody Map payload) { + String email = (String) payload.get("email"); + int code = Integer.parseInt(payload.get("code").toString()); + boolean isVerified = authService.verifyAuthCode(email, code); + if (isVerified) { + return ResponseEntity.ok(true); + } else { + return ResponseEntity.status(400).body(false); + } + } +} diff --git a/src/main/java/com/ssg/starroad/user/controller/OAuth2Controller.java b/src/main/java/com/ssg/starroad/user/controller/OAuth2Controller.java index 06c311a..c8e8459 100644 --- a/src/main/java/com/ssg/starroad/user/controller/OAuth2Controller.java +++ b/src/main/java/com/ssg/starroad/user/controller/OAuth2Controller.java @@ -1,27 +1,46 @@ package com.ssg.starroad.user.controller; +import com.ssg.starroad.config.jwt.TokenProvider; +import com.ssg.starroad.config.oauth.OAuth2SuccessHandler; +import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.service.CustomOAuth2UserService; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; @RestController @RequestMapping("/oauth2") +@RequiredArgsConstructor public class OAuth2Controller { private final CustomOAuth2UserService customOAuth2UserService; + private final TokenProvider tokenProvider; - public OAuth2Controller(CustomOAuth2UserService customOAuth2UserService) { - this.customOAuth2UserService = customOAuth2UserService; - } - @GetMapping("/callback") - public String handleOAuth2Callback(OAuth2AuthenticationToken authentication) { - // OAuth2AuthenticationToken에서 직접 OAuth2User를 받아오기 - OAuth2User oAuth2User = authentication.getPrincipal(); - customOAuth2UserService.processOAuthPostLogin(oAuth2User); - return "redirect:/main"; + @PostMapping("/{provider}") + public ResponseEntity oauth2Login(@PathVariable String provider, @RequestBody Map body) { + String code = body.get("code"); + OAuth2User oAuth2User = customOAuth2UserService.loadUserByProvider(provider, code); + + Map attributes = oAuth2User.getAttributes(); + String accessToken = (String) attributes.get("accessToken"); + String refreshToken = (String) attributes.get("refreshToken"); + String email = (String) attributes.get("email"); + + // Create a response object + Map response = new HashMap<>(); + response.put("accessToken", accessToken); + response.put("refreshToken", refreshToken); + response.put("email", email); + + return ResponseEntity.ok(response); } -} +} \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/controller/TokenApiController.java b/src/main/java/com/ssg/starroad/user/controller/TokenApiController.java index 8277cda..1501162 100644 --- a/src/main/java/com/ssg/starroad/user/controller/TokenApiController.java +++ b/src/main/java/com/ssg/starroad/user/controller/TokenApiController.java @@ -40,5 +40,4 @@ public ResponseEntity createAccessToken(@RequestBody // 클라이언트는 이 토큰을 사용하여 권한이 필요한 리소스에 접근 가능 } - } diff --git a/src/main/java/com/ssg/starroad/user/controller/UserController.java b/src/main/java/com/ssg/starroad/user/controller/UserController.java index 407eecc..332a70b 100644 --- a/src/main/java/com/ssg/starroad/user/controller/UserController.java +++ b/src/main/java/com/ssg/starroad/user/controller/UserController.java @@ -1,35 +1,33 @@ package com.ssg.starroad.user.controller; import com.ssg.starroad.config.jwt.TokenProvider; -import com.ssg.starroad.user.dto.LoginRequest; -import com.ssg.starroad.user.dto.LoginResponse; +import com.ssg.starroad.user.dto.*; import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.repository.RefreshTokenRepository; import com.ssg.starroad.user.service.RefreshTokenService; import com.ssg.starroad.user.service.UserService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.*; import java.util.Date; +import java.util.Map; +import java.util.Optional; @RequestMapping("/user") @RestController +@RequiredArgsConstructor public class UserController { private final UserService userService; private final TokenProvider tokenProvider; private final RefreshTokenService refreshTokenService; - - public UserController(UserService userService, TokenProvider tokenProvider, RefreshTokenService refreshTokenService) { - this.userService = userService; - this.tokenProvider = tokenProvider; - this.refreshTokenService = refreshTokenService; - } + private final RefreshTokenRepository refreshTokenRepository; @PostMapping("/login") public ResponseEntity login(@RequestBody LoginRequest request) { @@ -37,8 +35,8 @@ public ResponseEntity login(@RequestBody LoginRequest request) { if (isValidUser) { UserDetails userDetails = userService.loadUserByUsername(request.getEmail()); User user = userService.findByEmail(userDetails.getUsername()).orElse(null); - if (user == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + if (user == null || !user.isEnabled()) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new LoginResponse("비활성화 계정입니다. 이메일로 문의해주세요.")); } Date expiry = new Date(System.currentTimeMillis() + 600 * 1000); // 10 minutes @@ -49,9 +47,104 @@ public ResponseEntity login(@RequestBody LoginRequest request) { HttpHeaders headers = new HttpHeaders(); headers.add("Authorization", "Bearer " + accessToken); // Bearer 타입의 Authorization 헤더 추가 - LoginResponse loginResponse = new LoginResponse(accessToken, refreshToken); + LoginResponse loginResponse = new LoginResponse(accessToken, refreshToken,"환영합니다!"); return ResponseEntity.ok().headers(headers).body(loginResponse); } - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new LoginResponse("Invalid credentials")); + } + + @PostMapping("/join/email-check") + public ResponseEntity checkEmailDuplicate(@RequestBody Map request) { + boolean exists = userService.isEmailDuplicate(request.get("email")); + System.out.println(exists); + return ResponseEntity.ok(exists); + } + + @PostMapping("/join/nickname-check") + public ResponseEntity checkNicknameDuplicate(@RequestBody Map request) { + boolean exists = userService.isNicknameDuplicate(request.get("nickname")); + System.out.println(exists); + return ResponseEntity.ok(exists); + } + + @PostMapping("/join") + public ResponseEntity registerUser(@RequestBody UserDTO userDTO) { + try { + userService.registerUser(userDTO); + return ResponseEntity.ok("회원가입이 완료되었습니다."); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("회원가입 중 오류가 발생했습니다."); + } + } + + @PostMapping("/update-password") + public ResponseEntity updatePassword(@RequestBody PasswordUpdateRequest request) { + System.out.println("Received request to update password for email: " + request.getEmail()); // 로그 추가 + try { + boolean isUpdated = userService.updatePassword(request.getEmail(), request.getNewPassword()); + if (isUpdated) { + return ResponseEntity.ok("비밀번호가 성공적으로 변경되었습니다."); + } else { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("비밀번호 변경에 실패했습니다."); + } + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("비밀번호 변경 중 오류가 발생했습니다."); + } + } + + // 사용자 정보를 가져오는 메소드 + @PostMapping("/details") + public ResponseEntity getUserDetails(@RequestBody Map request) { + String email = request.get("email"); + Optional userOptional = userService.findByEmail(email); + if (userOptional.isPresent()) { + UserDTO userDTO = new UserDTO(userOptional.get()); + return ResponseEntity.ok(userDTO); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); + } + } + + @PostMapping("/update-profile") + public ResponseEntity updateProfile(@RequestBody UserDTO userDTO) { + try { + Optional userOptional = userService.findByEmail(userDTO.getEmail()); + if (userOptional.isPresent()) { + User user = userOptional.get(); + userService.updateUserProfile(user, userDTO); + return ResponseEntity.ok("프로필이 성공적으로 업데이트되었습니다."); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("사용자를 찾을 수 없습니다."); + } + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("프로필 업데이트 중 오류가 발생했습니다."); + } + } + + @Transactional + @PostMapping("/logout") + public ResponseEntity logout(@RequestBody LogoutRequest logoutRequest, HttpServletRequest request) { + String email = logoutRequest.getEmail(); + String accessToken = logoutRequest.getAccessToken(); + + if (tokenProvider.validToken(accessToken)) { + // 이메일과 토큰으로 사용자 인증 및 토큰 무효화 로직 추가 + refreshTokenRepository.deleteByUserEmail(email); + } + // 세션 무효화 + request.getSession().invalidate(); + + return ResponseEntity.ok().body("Logout successful"); + } + + @PostMapping("/inactive") + public ResponseEntity deactivateUser(@RequestBody Map request) { + String email = request.get("email"); + if (email == null || email.isEmpty()) { + return ResponseEntity.badRequest().body("Email is required"); + } + userService.inactiveUser(email); + return ResponseEntity.ok("User deactivated successfully"); + } } \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/dto/LoginResponse.java b/src/main/java/com/ssg/starroad/user/dto/LoginResponse.java index 2dcc876..c476e10 100644 --- a/src/main/java/com/ssg/starroad/user/dto/LoginResponse.java +++ b/src/main/java/com/ssg/starroad/user/dto/LoginResponse.java @@ -8,9 +8,14 @@ public class LoginResponse { private String accessToken; private String refreshToken; -} + private String message; + + public LoginResponse(String message) { + this.message = message; + } +} // 응답에서 리프레시 토큰을 포함하는 이유 --- // 사용자가 로그인할 때 마다 새로운 리프레시 토큰을 발급하는 것은, // 토큰의 보안을 강화하고, 오래된 또는 잠재적으로 노출된 리프레시 토큰을 무효화하는 효과적인 방법 diff --git a/src/main/java/com/ssg/starroad/user/dto/LogoutRequest.java b/src/main/java/com/ssg/starroad/user/dto/LogoutRequest.java new file mode 100644 index 0000000..2ab9fb4 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/dto/LogoutRequest.java @@ -0,0 +1,11 @@ +package com.ssg.starroad.user.dto; + +import lombok.Data; + +@Data +public class LogoutRequest { + private String email; + private String accessToken; + private String refreshToken; + +} diff --git a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java index 539a0c4..21fe4fd 100644 --- a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java +++ b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java @@ -39,6 +39,7 @@ private static OAuth2UserInfoDTO createGoogleDTO(OAuth2User oAuth2User, String p OAuth2UserInfoDTO dto = new OAuth2UserInfoDTO(); dto.setEmail(oAuth2User.getAttribute("email")); dto.setName(oAuth2User.getAttribute("name")); + dto.setNickname(oAuth2User.getAttribute("name")); dto.setImagePath(oAuth2User.getAttribute("picture")); dto.setProviderType(ProviderType.GOOGLE); dto.setProviderId(providerId); @@ -79,7 +80,7 @@ public User toEntity() { return User.builder() .email(this.email) .name(this.name) - .nickname(this.nickname) + .nickname(this.nickname != null ? this.nickname : this.email.split("@")[0]) // 기본 닉네임 설정 .imagePath(this.imagePath) .providerType(this.providerType) .providerId(this.providerId) diff --git a/src/main/java/com/ssg/starroad/user/dto/PasswordUpdateRequest.java b/src/main/java/com/ssg/starroad/user/dto/PasswordUpdateRequest.java new file mode 100644 index 0000000..0fedd6c --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/dto/PasswordUpdateRequest.java @@ -0,0 +1,16 @@ +package com.ssg.starroad.user.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PasswordUpdateRequest { + private String email; + private String newPassword; + +} diff --git a/src/main/java/com/ssg/starroad/user/dto/UserDTO.java b/src/main/java/com/ssg/starroad/user/dto/UserDTO.java index 5e7f248..632b816 100644 --- a/src/main/java/com/ssg/starroad/user/dto/UserDTO.java +++ b/src/main/java/com/ssg/starroad/user/dto/UserDTO.java @@ -1,5 +1,6 @@ package com.ssg.starroad.user.dto; +import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.enums.ActiveStatus; import com.ssg.starroad.user.enums.Gender; import com.ssg.starroad.user.enums.ProviderType; @@ -27,4 +28,21 @@ public class UserDTO { private int reviewExp; private int point; private ActiveStatus activeStatus; + + public UserDTO(User user) { + this.id = user.getId(); + this.password = user.getPassword(); + this.name = user.getName(); + this.nickname = user.getNickname(); + this.email = user.getEmail(); + this.gender = user.getGender(); + this.birth = user.getBirth(); + this.phone = user.getPhone(); + this.imagePath = user.getImagePath(); + this.providerType = user.getProviderType(); + this.providerId = user.getProviderId(); + this.reviewExp = user.getReviewExp(); + this.point = user.getPoint(); + this.activeStatus = user.getActiveStatus(); + } } diff --git a/src/main/java/com/ssg/starroad/user/entity/AuthCode.java b/src/main/java/com/ssg/starroad/user/entity/AuthCode.java new file mode 100644 index 0000000..d6c5ccc --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/entity/AuthCode.java @@ -0,0 +1,29 @@ +package com.ssg.starroad.user.entity; + + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class AuthCode { + + @Id + private String email; + private int code; + + // 기본 생성자 필요 + protected AuthCode() {} + + public AuthCode(String email, int code) { + this.email = email; + this.code = code; + } + + public String getEmail() { + return email; + } + + public int getCode() { + return code; + } +} \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/entity/RefreshToken.java b/src/main/java/com/ssg/starroad/user/entity/RefreshToken.java index ede1fea..0efe3a7 100644 --- a/src/main/java/com/ssg/starroad/user/entity/RefreshToken.java +++ b/src/main/java/com/ssg/starroad/user/entity/RefreshToken.java @@ -4,6 +4,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import java.time.Instant; @@ -28,6 +29,7 @@ public class RefreshToken { // 리프레시 토큰은 보안 상 중요한 정보 // 각 토큰 값은 유일무이, 사용자 인증이나 세션 관리 시 안전하게 사용 가능 + @Setter @Column(nullable = false) private Instant expiryDate; // 만료 시간은 토큰의 유효성을 검증할 때 중요한 역할 diff --git a/src/main/java/com/ssg/starroad/user/entity/User.java b/src/main/java/com/ssg/starroad/user/entity/User.java index bcb745a..ce26b3c 100644 --- a/src/main/java/com/ssg/starroad/user/entity/User.java +++ b/src/main/java/com/ssg/starroad/user/entity/User.java @@ -99,11 +99,16 @@ public String getName() { @Override public A getAttribute(String name) { switch (name) { - case "email": return (A) this.email; - case "name": return (A) this.name; - case "nickname": return (A) this.nickname; - case "imagePath": return (A) this.imagePath; - default: return null; // 해당하는 속성이 없을 경우 null 반환 + case "email": + return (A) this.email; + case "name": + return (A) this.name; + case "nickname": + return (A) this.nickname; + case "imagePath": + return (A) this.imagePath; + default: + return null; // 해당하는 속성이 없을 경우 null 반환 } } @@ -160,5 +165,24 @@ public boolean isEnabled() { // activeStatus가 ACTIVE인 경우에만 true 반환 return this.activeStatus == ActiveStatus.ACTIVE; } + // 로그인을 할 수 있는 전제가 회원의 상태가 active여야 함 + // 이때 true를 반환 + public void updatePassword(String newPassword) { + this.password = newPassword; + } + + // 닉네임, 비밀번호를 변경하는 메소드 추가 + public void updateProfile(String nickname, String password) { + if (nickname != null) { + this.nickname = nickname; + } + if (password != null) { + this.password = password; + } + } + + public void changeActiveStatus(ActiveStatus newStatus) { + this.activeStatus = newStatus; + } } diff --git a/src/main/java/com/ssg/starroad/user/enums/ActiveStatus.java b/src/main/java/com/ssg/starroad/user/enums/ActiveStatus.java index 27e7dd8..b3b00d9 100644 --- a/src/main/java/com/ssg/starroad/user/enums/ActiveStatus.java +++ b/src/main/java/com/ssg/starroad/user/enums/ActiveStatus.java @@ -6,11 +6,10 @@ public enum ActiveStatus { ACTIVE("활성화"), INACTIVE("비활성화"), - SUSPENDED("정지"), - CLOSED("탈퇴"), // 관리자에 의해 강제로 계정을 닫은 경우 - WITHDRAW("탈퇴"); + CLOSED("탈퇴"), // 유저가 자발적으로 탈퇴한 경우 + WITHDRAW("탈퇴"); private String desc; diff --git a/src/main/java/com/ssg/starroad/user/enums/ProviderType.java b/src/main/java/com/ssg/starroad/user/enums/ProviderType.java index 23e4492..dc0ea9b 100644 --- a/src/main/java/com/ssg/starroad/user/enums/ProviderType.java +++ b/src/main/java/com/ssg/starroad/user/enums/ProviderType.java @@ -1,7 +1,7 @@ package com.ssg.starroad.user.enums; public enum ProviderType { - NAVER, KAKAO, GOOGLE; + NAVER, KAKAO, GOOGLE, LOCAL; // 입력받은 문자열 providerId를 대문자로 변환 // ProviderType의 valueOf 메소드를 사용하여 해당 문자열과 일치하는 열거형 상수를 반환 diff --git a/src/main/java/com/ssg/starroad/user/repository/AuthCodeRepository.java b/src/main/java/com/ssg/starroad/user/repository/AuthCodeRepository.java new file mode 100644 index 0000000..7e32e78 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/repository/AuthCodeRepository.java @@ -0,0 +1,7 @@ +package com.ssg.starroad.user.repository; + +import com.ssg.starroad.user.entity.AuthCode; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuthCodeRepository extends JpaRepository { +} diff --git a/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java b/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java index b892ee1..6f4a683 100644 --- a/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java +++ b/src/main/java/com/ssg/starroad/user/repository/RefreshTokenRepository.java @@ -9,8 +9,12 @@ public interface RefreshTokenRepository extends JpaRepository { Optional findByUser_Id(Long userId); + Optional findByToken(String refreshToken); + void deleteByUserEmail(String email); + + // 테스트 용 @Transactional void deleteByUserId(Long userId); } diff --git a/src/main/java/com/ssg/starroad/user/repository/UserRepository.java b/src/main/java/com/ssg/starroad/user/repository/UserRepository.java index 2fa4ddb..c6ac561 100644 --- a/src/main/java/com/ssg/starroad/user/repository/UserRepository.java +++ b/src/main/java/com/ssg/starroad/user/repository/UserRepository.java @@ -8,4 +8,5 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); + boolean existsByNickname(String nickname); } diff --git a/src/main/java/com/ssg/starroad/user/service/AuthService.java b/src/main/java/com/ssg/starroad/user/service/AuthService.java new file mode 100644 index 0000000..d10582b --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/AuthService.java @@ -0,0 +1,8 @@ +package com.ssg.starroad.user.service; + +import jakarta.mail.MessagingException; + +public interface AuthService { + void sendAuthCode(String email) throws MessagingException; + boolean verifyAuthCode(String email, int authCode); +} diff --git a/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java b/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java index fc7f394..d06d788 100644 --- a/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java +++ b/src/main/java/com/ssg/starroad/user/service/CustomOAuth2UserService.java @@ -5,12 +5,16 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import java.util.Optional; + public interface CustomOAuth2UserService extends OAuth2UserService { OAuth2User loadUser(OAuth2UserRequest userRequest); User findUserByEmail(String email); - void processOAuthPostLogin(OAuth2User oAuth2User); + Optional processOAuthPostLogin(OAuth2User oAuth2User); + + OAuth2User loadUserByProvider(String provider, String code); } // OAuth2 프로세스는 사용자가 예를 들어 Google이나 Facebook 같은 외부 서비스를 사용하여 로그인할 때 발생함 diff --git a/src/main/java/com/ssg/starroad/user/service/EmailService.java b/src/main/java/com/ssg/starroad/user/service/EmailService.java new file mode 100644 index 0000000..b2bd066 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/EmailService.java @@ -0,0 +1,8 @@ +package com.ssg.starroad.user.service; + +import jakarta.mail.MessagingException; + +public interface EmailService { + void sendEmail(String to, String subject, String text) throws MessagingException; +} + diff --git a/src/main/java/com/ssg/starroad/user/service/UserService.java b/src/main/java/com/ssg/starroad/user/service/UserService.java index 1e38fd6..0ee42d6 100644 --- a/src/main/java/com/ssg/starroad/user/service/UserService.java +++ b/src/main/java/com/ssg/starroad/user/service/UserService.java @@ -15,4 +15,16 @@ public interface UserService extends UserDetailsService { Long save(UserDTO userDTO); boolean validateUser(String email, String password); + + boolean isEmailDuplicate(String email); // 이메일 중복 검사 + + boolean isNicknameDuplicate(String nickname); // 닉네임 중복 검사 + + void registerUser(UserDTO userDTO); + + boolean updatePassword(String email, String newPassword); + + void updateUserProfile(User user, UserDTO userDTO); + + void inactiveUser(String email); } diff --git a/src/main/java/com/ssg/starroad/user/service/impl/AuthServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/AuthServiceImpl.java new file mode 100644 index 0000000..62f3056 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/impl/AuthServiceImpl.java @@ -0,0 +1,45 @@ +package com.ssg.starroad.user.service.impl; + +import com.ssg.starroad.user.entity.AuthCode; +import com.ssg.starroad.user.repository.AuthCodeRepository; +import com.ssg.starroad.user.service.AuthService; +import com.ssg.starroad.user.service.EmailService; +import jakarta.mail.MessagingException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Optional; +import java.util.Random; + +@Service +public class AuthServiceImpl implements AuthService { + + private final AuthCodeRepository authCodeRepository; + private final EmailService emailService; + private final Random random = new Random(); + + @Autowired + public AuthServiceImpl(AuthCodeRepository authCodeRepository, EmailService emailService) { + this.authCodeRepository = authCodeRepository; + this.emailService = emailService; + } + + @Override + public void sendAuthCode(String email) throws MessagingException { + int authCode = random.nextInt(9000) + 1000; // 4자리 랜덤 숫자 생성 + AuthCode code = new AuthCode(email, authCode); + authCodeRepository.save(code); + + emailService.sendEmail(email, "인증 코드", "인증 코드는 " + authCode + " 입니다."); + } + + @Override + public boolean verifyAuthCode(String email, int authCode) { + Optional storedCode = authCodeRepository.findById(email); + if (storedCode.isPresent() && storedCode.get().getCode() == authCode) { + authCodeRepository.delete(storedCode.get()); + return true; + } + return false; + } +} diff --git a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java index 5974d6d..134e228 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java @@ -1,87 +1,177 @@ package com.ssg.starroad.user.service.impl; +import com.ssg.starroad.config.jwt.TokenProvider; +import com.ssg.starroad.user.dto.OAuth2UserInfoDTO; import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.enums.ProviderType; import com.ssg.starroad.user.repository.UserRepository; import com.ssg.starroad.user.service.CustomOAuth2UserService; +import com.ssg.starroad.user.service.RefreshTokenService; import com.ssg.starroad.user.service.oauth.OAuth2UserData; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.http.*; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @RequiredArgsConstructor @Service public class CustomOAuth2UserServiceImpl extends DefaultOAuth2UserService implements CustomOAuth2UserService { private final UserRepository userRepository; - private final OAuth2UserData oauth2UserData; + private final TokenProvider tokenProvider; + private final RefreshTokenService refreshTokenService; + private final ClientRegistrationRepository clientRegistrationRepository; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) { OAuth2User oAuth2User = super.loadUser(userRequest); String provider = userRequest.getClientRegistration().getRegistrationId(); - String providerId = oAuth2User.getName(); + String providerId = getProviderId(oAuth2User, provider); - User user = oauth2UserData.oauth2Info(oAuth2User, provider, providerId); + OAuth2UserInfoDTO userInfoDTO = OAuth2UserInfoDTO.fromOAuth2User(oAuth2User, provider, providerId); - if (user.getEmail() == null) { - throw new IllegalArgumentException("OAuth2 제공자로부터 이메일을 가져올 수 없습니다."); + Optional existingUser = userRepository.findByEmail(userInfoDTO.getEmail()); + User user; + if (existingUser.isPresent()) { + user = updateExistingUser(existingUser.get(), userInfoDTO); + } else { + String nickname = userInfoDTO.getNickname() != null ? userInfoDTO.getNickname() : userInfoDTO.getEmail().split("@")[0]; + user = User.builder() + .email(userInfoDTO.getEmail()) + .nickname(nickname) + .build(); + user = userRepository.save(user); } - User savedUser = userRepository.findByEmail(user.getEmail()) - .map(existingUser -> updateExistingUser(existingUser, oAuth2User)) - .orElseGet(() -> user); + String accessToken = tokenProvider.generateAccessToken(user); + String refreshToken = refreshTokenService.createRefreshToken(user.getId()); - userRepository.save(savedUser); - return createOAuth2User(savedUser); + // Create a response object that includes the accessToken, refreshToken, and email + Map response = new HashMap<>(); + response.put("accessToken", accessToken); + response.put("refreshToken", refreshToken); + response.put("email", user.getEmail()); + + return new DefaultOAuth2User( + oAuth2User.getAuthorities(), + response, + "email" + ); } - private OAuth2User createOAuth2User(User user) { - Map attributes = new HashMap<>(); - attributes.put("email", user.getEmail()); - attributes.put("name", user.getName()); - attributes.put("nickname", user.getNickname()); - attributes.put("phone", user.getPhone()); - attributes.put("imagePath", user.getImagePath()); - - List authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); - return new DefaultOAuth2User(authorities, attributes, "email"); + + private String getProviderId(OAuth2User oAuth2User, String provider) { + switch (provider.toUpperCase()) { + case "GOOGLE": + return oAuth2User.getAttribute("sub"); + case "KAKAO": + Long kakaoId = oAuth2User.getAttribute("id"); + return kakaoId != null ? String.valueOf(kakaoId) : null; + case "NAVER": + Map response = oAuth2User.getAttribute("response"); + if (response != null) { + return (String) response.get("id"); + } else { + throw new IllegalArgumentException("OAuth2 제공자로부터 providerId를 가져올 수 없습니다."); + } + default: + throw new IllegalArgumentException("Unsupported provider: " + provider); + } } + @Override public User findUserByEmail(String email) { return userRepository.findByEmail(email) .orElseThrow(() -> new IllegalArgumentException("No user found with email " + email)); } - // OAuth2 로그인 후 사용자 처리 + private User updateExistingUser(User existingUser, OAuth2UserInfoDTO userInfoDTO) { + return existingUser.toBuilder() + .name(userInfoDTO.getName()) + .nickname(userInfoDTO.getNickname() != null ? userInfoDTO.getNickname() : existingUser.getNickname()) + .imagePath(userInfoDTO.getImagePath()) + .providerType(userInfoDTO.getProviderType()) + .providerId(userInfoDTO.getProviderId()) + .build(); + } + + @Override - public void processOAuthPostLogin(OAuth2User oAuth2User) { - String provider = oAuth2User.getAttribute("provider"); - String providerId = oAuth2User.getName(); - User user = oauth2UserData.oauth2Info(oAuth2User, provider, providerId); - userRepository.save(user); // 사용자 정보 저장 + public OAuth2User loadUserByProvider(String provider, String code) { + OAuth2AccessToken accessToken = getAccessToken(provider, code); + OAuth2UserRequest userRequest = createOAuth2UserRequest(provider, accessToken); + return loadUser(userRequest); } - private User updateExistingUser(User existingUser, OAuth2User oAuth2User) { - User updatedUser = existingUser.toBuilder() - .name(oAuth2User.getAttribute("name")) - .nickname(oAuth2User.getAttribute("nickname")) - .phone(oAuth2User.getAttribute("phone")) - .imagePath(oAuth2User.getAttribute("picture")) - .providerType(ProviderType.fromString(oAuth2User.getAttribute("provider"))) - .providerId(oAuth2User.getName()) - .build(); - userRepository.save(updatedUser); // 변경된 사용자 정보 저장 - return updatedUser; + private OAuth2AccessToken getAccessToken(String provider, String code) { + ClientRegistration clientRegistration = getClientRegistration(provider); + RestTemplate restTemplate = new RestTemplate(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("grant_type", "authorization_code"); + params.add("client_id", clientRegistration.getClientId()); + params.add("redirect_uri", clientRegistration.getRedirectUri()); + params.add("code", code); + params.add("client_secret", clientRegistration.getClientSecret()); + + HttpEntity> request = new HttpEntity<>(params, headers); + ResponseEntity response; + + try { + response = restTemplate.postForEntity(clientRegistration.getProviderDetails().getTokenUri(), request, Map.class); + System.out.println("Token request status: " + response.getStatusCode()); + System.out.println("Token request response body: " + response.getBody()); + } catch (Exception e) { + System.out.println("Failed to retrieve access token: " + e.getMessage()); + throw new OAuth2AuthenticationException("Failed to retrieve access token"); + } + + if (response.getStatusCode() == HttpStatus.OK) { + Map responseBody = response.getBody(); + System.out.println("Access token response: " + responseBody); // 응답 로그 추가 + String accessToken = (String) responseBody.get("access_token"); + return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, accessToken, null, null); + } else { + System.out.println("Failed to retrieve access token: " + response.getStatusCode()); + throw new OAuth2AuthenticationException("Failed to retrieve access token"); + } + } + + private OAuth2UserRequest createOAuth2UserRequest(String provider, OAuth2AccessToken accessToken) { + ClientRegistration clientRegistration = getClientRegistration(provider); + return new OAuth2UserRequest(clientRegistration, accessToken); + } + + private ClientRegistration getClientRegistration(String provider) { + return clientRegistrationRepository.findByRegistrationId(provider); } -} + @Override + public Optional processOAuthPostLogin(OAuth2User oAuth2User) { + String email = oAuth2User.getAttribute("email"); + Optional userOptional = userRepository.findByEmail(email); + + if (userOptional.isEmpty()) { + OAuth2UserInfoDTO dto = OAuth2UserInfoDTO.fromOAuth2User(oAuth2User, oAuth2User.getAttribute("provider"), oAuth2User.getName()); + User user = dto.toEntity(); + user = userRepository.save(user); + return Optional.of(user); + } + + return userOptional; + } +} diff --git a/src/main/java/com/ssg/starroad/user/service/impl/EmailServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/EmailServiceImpl.java new file mode 100644 index 0000000..8b7d342 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/service/impl/EmailServiceImpl.java @@ -0,0 +1,30 @@ +package com.ssg.starroad.user.service.impl; + +import com.ssg.starroad.user.service.EmailService; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +import jakarta.mail.MessagingException; + +@RequiredArgsConstructor +@Service +public class EmailServiceImpl implements EmailService { + + private final JavaMailSender javaMailSender; + + + @Override + public void sendEmail(String to, String subject, String text) throws MessagingException { + MimeMessage message = javaMailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true,"UTF-8"); + + helper.setTo(to); + helper.setSubject(subject); + helper.setText(text, true); + + javaMailSender.send(message); + } +} diff --git a/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java index 68cd7c3..cc215d2 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/RefreshTokenServiceImpl.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Service; import java.time.Instant; +import java.util.Optional; @RequiredArgsConstructor @Service @@ -37,13 +38,25 @@ public String createRefreshToken(Long userId) { // 리프레시 토큰의 만료 시간 설정 Instant expiryDate = Instant.now().plusSeconds(7 * 24 * 3600); // 7일 후 만료 - // 새로운 RefreshToken 객체 생성 - RefreshToken refreshToken = new RefreshToken(user, generateUniqueToken(), expiryDate); + // 고유한 토큰 값을 생성 + String newToken = generateUniqueToken(); - // 생성된 리프레시 토큰을 데이터베이스에 저장 - refreshTokenRepository.save(refreshToken); + // 기존의 리프레시 토큰이 있는지 확인 + Optional existingTokenOptional = refreshTokenRepository.findByUser_Id(userId); - return refreshToken.getToken(); // 직접 토큰 문자열 반환 + if (existingTokenOptional.isPresent()) { + // 기존 토큰이 존재하면 업데이트 + RefreshToken existingToken = existingTokenOptional.get(); + existingToken.updateToken(newToken); + existingToken.setExpiryDate(expiryDate); + refreshTokenRepository.save(existingToken); + return existingToken.getToken(); + } else { + // 기존 토큰이 존재하지 않으면 새로 생성 + RefreshToken refreshToken = new RefreshToken(user, newToken, expiryDate); + refreshTokenRepository.save(refreshToken); + return refreshToken.getToken(); + } } // 고유한 토큰 값을 생성하는 데 사용. 특히 리프레시 토큰 내에서 고유성을 보장하는 데 사용 diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index 69f6a68..db906c8 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -2,16 +2,17 @@ import com.ssg.starroad.user.dto.UserDTO; import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.enums.ProviderType; +import com.ssg.starroad.user.enums.ActiveStatus; import com.ssg.starroad.user.repository.UserRepository; import com.ssg.starroad.user.service.UserService; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; import java.util.Optional; @Service @@ -19,6 +20,7 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; + private final BCryptPasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { @@ -26,12 +28,8 @@ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundExcep User user = userOptional.orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email)); - return new org.springframework.security.core.userdetails.User( - user.getEmail(), - user.getPassword(), - Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); + return user; // User 엔티티 반환 } - // Collections.singletonList 단일 항목을 포함하는 변경 불가능한 리스트 생성. 단일 권한 처리 // 리프레시 토큰을 이용해 새로운 액세스 토큰을 생성할 때 해당 사용자의 정보를 db에서 조회하기 위해 // User 클래스에서 id 필드에 해당하며, @Id 어노테이션으로 지정 @@ -51,18 +49,16 @@ public Optional findByEmail(String email) { @Override public Long save(UserDTO userDTO) { - BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); - User user = User.builder() .email(userDTO.getEmail()) - .password(encoder.encode(userDTO.getPassword())) + .password(passwordEncoder.encode(userDTO.getPassword())) .name(userDTO.getName()) .nickname(userDTO.getNickname()) .imagePath(userDTO.getImagePath()) .gender(userDTO.getGender()) .birth(userDTO.getBirth()) .phone(userDTO.getPhone()) - .providerType(userDTO.getProviderType()) + .providerType(ProviderType.LOCAL) .providerId(userDTO.getProviderId()) .reviewExp(userDTO.getReviewExp()) .point(userDTO.getPoint()) @@ -73,7 +69,7 @@ public Long save(UserDTO userDTO) { public boolean validateUser(String email, String password) { Optional userOpt = userRepository.findByEmail(email); - if (!userOpt.isPresent()) { + if (userOpt.isEmpty()) { System.out.println("No user found with email: " + email); return false; } @@ -88,4 +84,75 @@ public boolean validateUser(String email, String password) { return passwordMatches; } + + @Override + public boolean isEmailDuplicate(String email) { + return userRepository.findByEmail(email).isPresent(); + } + + @Override + public boolean isNicknameDuplicate(String nickname) { + return userRepository.existsByNickname(nickname); + } + + @Override + public void registerUser(UserDTO userDTO) { + User user = User.builder() + .name(userDTO.getName()) + .nickname(userDTO.getNickname()) + .email(userDTO.getEmail()) + .password(passwordEncoder.encode(userDTO.getPassword())) + .gender(userDTO.getGender()) + .birth(userDTO.getBirth()) + .phone(userDTO.getPhone()) + .providerType(ProviderType.LOCAL) + .providerId(null) + .reviewExp(0) + .point(0) + .activeStatus(ActiveStatus.ACTIVE) + .build(); + + userRepository.save(user); + } + + @Override + public boolean updatePassword(String email, String newPassword) { + System.out.println("Searching for user with email: " + email); // 로그 추가 + User user = userRepository.findByEmail(email).orElse(null); + if (user != null) { + System.out.println("User found: " + user); // 로그 추가 + user.updatePassword(passwordEncoder.encode(newPassword)); + userRepository.save(user); + return true; + } + System.out.println("User not found with email: " + email); // 로그 추가 + return false; + } + + @Transactional + @Override + public void updateUserProfile(User user, UserDTO userDTO) { + String encodedPassword = user.getPassword(); // 기존 비밀번호로 초기화 + + if (userDTO.getPassword() != null && !userDTO.getPassword().isEmpty()) { + // 비밀번호가 비어있지 않을 경우에만 비밀번호 업데이트 + encodedPassword = passwordEncoder.encode(userDTO.getPassword()); + } + + user.updateProfile(userDTO.getNickname(), encodedPassword); + + userRepository.save(user); + System.out.println("Updated User: " + user); + System.out.println("Updated Nickname: " + user.getNickname()); + System.out.println("Updated Password: " + user.getPassword()); + } + + @Override + public void inactiveUser(String email) { + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + user.changeActiveStatus(ActiveStatus.INACTIVE); + userRepository.save(user); + } } diff --git a/src/test/java/com/ssg/starroad/user/controller/UserControllerTest.java b/src/test/java/com/ssg/starroad/user/controller/UserControllerTest.java index e357a50..f5d0234 100644 --- a/src/test/java/com/ssg/starroad/user/controller/UserControllerTest.java +++ b/src/test/java/com/ssg/starroad/user/controller/UserControllerTest.java @@ -24,11 +24,9 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; From 450e07920275a7d77898806cb8d8b582280cf984 Mon Sep 17 00:00:00 2001 From: mariasoo <9723maria@gmail.com> Date: Fri, 24 May 2024 17:27:11 +0900 Subject: [PATCH 11/49] login : fix security --- build.gradle | 4 ++-- .../java/com/ssg/starroad/user/controller/UserController.java | 2 +- src/main/java/com/ssg/starroad/user/entity/User.java | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index adaeea6..07b03b4 100644 --- a/build.gradle +++ b/build.gradle @@ -23,8 +23,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' -// implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' -// implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-mail:3.0.5' implementation 'com.sun.mail:jakarta.mail:2.0.1' // 버전을 2.0.1로 변경 diff --git a/src/main/java/com/ssg/starroad/user/controller/UserController.java b/src/main/java/com/ssg/starroad/user/controller/UserController.java index 5f303ad..f011c0a 100644 --- a/src/main/java/com/ssg/starroad/user/controller/UserController.java +++ b/src/main/java/com/ssg/starroad/user/controller/UserController.java @@ -153,7 +153,7 @@ public ResponseEntity deactivateUser(@RequestBody Map re return ResponseEntity.ok("User deactivated successfully"); } -} + @GetMapping("/mypage/{userId}") diff --git a/src/main/java/com/ssg/starroad/user/entity/User.java b/src/main/java/com/ssg/starroad/user/entity/User.java index ecd8fa6..6a84435 100644 --- a/src/main/java/com/ssg/starroad/user/entity/User.java +++ b/src/main/java/com/ssg/starroad/user/entity/User.java @@ -36,7 +36,6 @@ @NoArgsConstructor(access = PROTECTED) @AllArgsConstructor @Embeddable -@Builder public class User extends BaseTimeEntity implements UserDetails, OAuth2User { @@ -188,7 +187,7 @@ public void updateProfile(String nickname, String password) { public void changeActiveStatus(ActiveStatus newStatus) { this.activeStatus = newStatus; - + } public void setProfileimgPath(String path){ this.imagePath=path; From cce968963fec49404250235c0892551d72a30bc5 Mon Sep 17 00:00:00 2001 From: HRiver Date: Fri, 24 May 2024 21:46:37 +0900 Subject: [PATCH 12/49] fix: cors --- src/main/java/com/ssg/starroad/config/WebConfigProd.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/config/WebConfigProd.java b/src/main/java/com/ssg/starroad/config/WebConfigProd.java index 1a74cc4..93c46ed 100644 --- a/src/main/java/com/ssg/starroad/config/WebConfigProd.java +++ b/src/main/java/com/ssg/starroad/config/WebConfigProd.java @@ -14,6 +14,7 @@ public void addCorsMappings(CorsRegistry registry) { .allowedOrigins("http://10.0.15.6", "http://223.130.155.196") .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") .allowedHeaders("*") - .allowCredentials(true); + .allowCredentials(true) + .exposedHeaders("Authorization", "refreshToken"); // 서버의 응답에서 노출할 헤더 추가; } } From 5385a630845df36d93ec6b77f857e01df35b24c3 Mon Sep 17 00:00:00 2001 From: HRiver Date: Fri, 24 May 2024 23:06:59 +0900 Subject: [PATCH 13/49] fix: websecurity --- src/main/java/com/ssg/starroad/config/WebConfigDev.java | 3 ++- src/main/java/com/ssg/starroad/config/WebSecurityConfig.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/config/WebConfigDev.java b/src/main/java/com/ssg/starroad/config/WebConfigDev.java index 88ccd93..9ffa676 100644 --- a/src/main/java/com/ssg/starroad/config/WebConfigDev.java +++ b/src/main/java/com/ssg/starroad/config/WebConfigDev.java @@ -14,6 +14,7 @@ public void addCorsMappings(CorsRegistry registry) { .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") .allowedHeaders("*") - .allowCredentials(true); + .allowCredentials(true) + .exposedHeaders("Authorization", "refreshToken"); // 서버의 응답에서 노출할 헤더 추가;; } } diff --git a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java index 1d75efe..443e857 100644 --- a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java +++ b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java @@ -61,6 +61,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/user/login","/user/logout","/user/join/**","/oauth2/**","/api/token/**","/auth/**").permitAll() + .requestMatchers("/api/oauth2/**").permitAll() .requestMatchers("/user/update-profile","/user/update-password","/user/details","/user/inactive").authenticated() .anyRequest().authenticated()) .oauth2Login(oauth -> oauth From 6b798ce3770de9d1e16421887bcb4b1f8cdb533a Mon Sep 17 00:00:00 2001 From: HRiver Date: Fri, 24 May 2024 23:53:41 +0900 Subject: [PATCH 14/49] fix: webconfig --- src/main/java/com/ssg/starroad/config/WebConfigProd.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/config/WebConfigProd.java b/src/main/java/com/ssg/starroad/config/WebConfigProd.java index 93c46ed..2c30933 100644 --- a/src/main/java/com/ssg/starroad/config/WebConfigProd.java +++ b/src/main/java/com/ssg/starroad/config/WebConfigProd.java @@ -11,7 +11,7 @@ public class WebConfigProd implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://10.0.15.6", "http://223.130.155.196") + .allowedOrigins("http://10.0.15.6:8080", "http://223.130.155.196:8080") .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) From 69a2842771ae196617e56cc6d95d53135007680a Mon Sep 17 00:00:00 2001 From: HRiver Date: Sat, 25 May 2024 00:01:31 +0900 Subject: [PATCH 15/49] fix: fix userRegister --- .../com/ssg/starroad/user/service/impl/UserServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index c388db7..8310993 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -118,6 +118,8 @@ public void registerUser(UserDTO userDTO) { .point(0) .activeStatus(ActiveStatus.ACTIVE) .build(); + + userRepository.save(user); } @Override From 6b4195c9f35dbffb0107c1df8b6d9301b04471bf Mon Sep 17 00:00:00 2001 From: HRiver Date: Sat, 25 May 2024 00:40:54 +0900 Subject: [PATCH 16/49] fix: fix corsconfig --- src/main/java/com/ssg/starroad/config/CorsConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/ssg/starroad/config/CorsConfig.java b/src/main/java/com/ssg/starroad/config/CorsConfig.java index ba85e82..766079f 100644 --- a/src/main/java/com/ssg/starroad/config/CorsConfig.java +++ b/src/main/java/com/ssg/starroad/config/CorsConfig.java @@ -13,7 +13,9 @@ public UrlBasedCorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); + config.addAllowedOriginPattern("http://223.130.155.196:8080"); // 프로덕션용 config.addAllowedOriginPattern("http://localhost:8080"); // Vue 애플리케이션 주소 추가 + config.addAllowedHeader("*"); // 서버가 받을 수 있는 모든 헤더를 정의 // 각 HTTP 메소드를 개별적으로 추가 From aec5db0422fba5d8658fc3dfe8e64c5727a8f0da Mon Sep 17 00:00:00 2001 From: choijh9023 Date: Sat, 25 May 2024 09:39:52 +0900 Subject: [PATCH 17/49] stash --- .../review/repository/ReviewLikeRepository.java | 17 ++++++++++++++++- .../shop/controller/StoreController.java | 9 +++++---- .../shop/repository/StoreRepository.java | 3 +++ .../shop/service/impl/StoreServiceImpl.java | 4 +++- .../shop/controller/StoreControllerTest.java | 2 +- .../shop/service/impl/StoreServiceImplTest.java | 12 ++++++------ 6 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/ssg/starroad/review/repository/ReviewLikeRepository.java b/src/main/java/com/ssg/starroad/review/repository/ReviewLikeRepository.java index 2383fa8..ab12b44 100644 --- a/src/main/java/com/ssg/starroad/review/repository/ReviewLikeRepository.java +++ b/src/main/java/com/ssg/starroad/review/repository/ReviewLikeRepository.java @@ -11,10 +11,25 @@ import java.util.Optional; public interface ReviewLikeRepository extends JpaRepository { + // 주어진 userId와 reviewId로 ReviewLike 엔티티를 조회합니다. +// userId와 reviewId를 기준으로 ReviewLike를 찾습니다. 존재하지 않을 경우 Optional.empty()를 반환합니다. Optional findByUserIdAndReviewId(Long userId, Long reviewId); + + // 주어진 userId와 reviewId로 ReviewLike 엔티티를 삭제합니다. +// userId와 reviewId를 기준으로 ReviewLike를 삭제합니다. void deleteByUserIdAndReviewId(Long userId, Long reviewId); + + // 주어진 userId로 사용자가 좋아요를 누른 리뷰 ID 목록을 조회합니다. +// JPQL 쿼리를 사용하여 userId를 기준으로 사용자가 좋아요를 누른 리뷰 ID 목록을 조회하여 반환합니다. @Query("SELECT rl.review.id FROM ReviewLike rl WHERE rl.user.id = :userId") - List findLikedReviewIdsByUserId(@Param("userId")Long userId); + List findLikedReviewIdsByUserId(@Param("userId") Long userId); + + // 주어진 User와 Review로 ReviewLike 존재 여부를 확인합니다. +// User와 Review를 기준으로 ReviewLike의 존재 여부를 확인하여 반환합니다. boolean existsByUserAndReview(User user, Review review); + + // 주어진 userId와 reviewId로 ReviewLike 존재 여부를 확인합니다. +// userId와 reviewId를 기준으로 ReviewLike의 존재 여부를 확인하여 반환합니다. boolean existsByUser_IdAndReview_Id(Long userId, Long reviewId); + } diff --git a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java index 165fbef..c018009 100644 --- a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java +++ b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java @@ -32,14 +32,15 @@ public ResponseEntity> StoreSearch() { @GetMapping("/{id}/reviews") public ResponseEntity StoreGetWithReviewById( @PathVariable Long id, - @RequestParam String userEmail, // userId 파라미터 추 + @RequestParam String userEmail, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { try { - String test = "hklee@example.com"; - StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, test, page, size); + log.info("Received request for store ID: {}, userEmail: {}, page: {}, size: {}", id, userEmail, page, size); + StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, userEmail, page, size); return ResponseEntity.ok(storeWithReviewDTO); } catch (RuntimeException e) { + log.error("리뷰가 있는 상점을 가져오는데 실패했습니다", e); return ResponseEntity.notFound().build(); } } @@ -59,4 +60,4 @@ public ResponseEntity StoreGetGuidemapById(@PathVariable Long id) { return ResponseEntity.notFound().build(); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/shop/repository/StoreRepository.java b/src/main/java/com/ssg/starroad/shop/repository/StoreRepository.java index e2cd7c5..893cdd0 100644 --- a/src/main/java/com/ssg/starroad/shop/repository/StoreRepository.java +++ b/src/main/java/com/ssg/starroad/shop/repository/StoreRepository.java @@ -12,6 +12,9 @@ public interface StoreRepository extends JpaRepository { // complexShoppingmallId에 해당하는 Store 목록을 조회하는 메서드 // Optional>: 결과가 없을 경우 빈 Optional을 반환하고, 결과가 있을 경우 Store의 리스트를 Optional로 감싸서 반환 Optional> findByComplexShoppingmallId(Long id); + // store 정보를 id를 기반으로 조회해서 가져오는 메서드 +// 주어진 id를 기준으로 Store 엔티티를 조회하여 반환합니다. 존재하지 않을 경우 Optional.empty()를 반환합니다. Optional findById(Long id); + } \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java index af1e17d..4c55350 100644 --- a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java +++ b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java @@ -128,11 +128,13 @@ public StoreDTO findStore(Long id) { .storeGuideMap(store.getStoreGuideMap()) .name(store.getName()) .floor(store.getFloor().getFloor()) - .build(); + .build(); return storeDTO; } + + } \ No newline at end of file diff --git a/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java b/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java index 7a12360..929ea6e 100644 --- a/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java +++ b/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java @@ -25,7 +25,7 @@ public void teststoreController() { @Test public void teststoreController2() { log.info( - storeController.StoreGetWithReviewById(3L,"hklee@example.com",1,20)); + storeController.StoreGetWithReviewById(1L,"hklee@example.com",1,20)); } @Test diff --git a/src/test/java/com/ssg/starroad/shop/service/impl/StoreServiceImplTest.java b/src/test/java/com/ssg/starroad/shop/service/impl/StoreServiceImplTest.java index 873b67f..027dcd0 100644 --- a/src/test/java/com/ssg/starroad/shop/service/impl/StoreServiceImplTest.java +++ b/src/test/java/com/ssg/starroad/shop/service/impl/StoreServiceImplTest.java @@ -39,12 +39,12 @@ void getStore() { log.info(stores.toString()); } - @Test - @DisplayName("Store단일 매장 전체 리뷰 작성메소드 테스트") - void StoreWithAllReviewTest() { - log.info(storeService.findStoreWithReview(2L,"hklee@example.com",1,20)); - - } +// @Test +// @DisplayName("Store단일 매장 전체 리뷰 작성메소드 테스트") +// void StoreWithAllReviewTest() { +// log.info(storeService.findStoreWithReview(2L,"hklee@example.com",1,20)); +// +// } From c14823d4669aecda4c92c3b3a47dd10383bfc63b Mon Sep 17 00:00:00 2001 From: HRiver Date: Sat, 25 May 2024 16:36:43 +0900 Subject: [PATCH 18/49] fix: oauth db method --- .../java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java | 2 +- .../user/service/impl/CustomOAuth2UserServiceImpl.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java index 21fe4fd..88a74dc 100644 --- a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java +++ b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java @@ -80,7 +80,7 @@ public User toEntity() { return User.builder() .email(this.email) .name(this.name) - .nickname(this.nickname != null ? this.nickname : this.email.split("@")[0]) // 기본 닉네임 설정 + .nickname(this.nickname != null ? this.nickname : this.email.replaceAll("(@.*)","")) // 기본 닉네임 설정 .imagePath(this.imagePath) .providerType(this.providerType) .providerId(this.providerId) diff --git a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java index 134e228..9ecbaf2 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java @@ -3,6 +3,7 @@ import com.ssg.starroad.config.jwt.TokenProvider; import com.ssg.starroad.user.dto.OAuth2UserInfoDTO; import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.enums.ActiveStatus; import com.ssg.starroad.user.enums.ProviderType; import com.ssg.starroad.user.repository.UserRepository; import com.ssg.starroad.user.service.CustomOAuth2UserService; @@ -47,10 +48,14 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) { if (existingUser.isPresent()) { user = updateExistingUser(existingUser.get(), userInfoDTO); } else { - String nickname = userInfoDTO.getNickname() != null ? userInfoDTO.getNickname() : userInfoDTO.getEmail().split("@")[0]; + String nickname = userInfoDTO.getNickname() != null ? userInfoDTO.getNickname() : userInfoDTO.getEmail().replaceAll("(@.*)",""); user = User.builder() .email(userInfoDTO.getEmail()) + .name(userInfoDTO.getName()) .nickname(nickname) + .providerId(userInfoDTO.getProviderId()) + .providerType(userInfoDTO.getProviderType()) + .activeStatus(ActiveStatus.ACTIVE) .build(); user = userRepository.save(user); } From 4ac7ef6132b6bc3492dd658787456d07bb5c2c2a Mon Sep 17 00:00:00 2001 From: HRiver Date: Sat, 25 May 2024 16:49:49 +0900 Subject: [PATCH 19/49] fix: oauth db method --- src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java | 2 +- .../starroad/user/service/impl/CustomOAuth2UserServiceImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java index 88a74dc..5def653 100644 --- a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java +++ b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java @@ -80,7 +80,7 @@ public User toEntity() { return User.builder() .email(this.email) .name(this.name) - .nickname(this.nickname != null ? this.nickname : this.email.replaceAll("(@.*)","")) // 기본 닉네임 설정 + .nickname(this.nickname != null ? this.nickname : this.email) // 기본 닉네임 설정 .imagePath(this.imagePath) .providerType(this.providerType) .providerId(this.providerId) diff --git a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java index 9ecbaf2..d59a574 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java @@ -48,7 +48,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) { if (existingUser.isPresent()) { user = updateExistingUser(existingUser.get(), userInfoDTO); } else { - String nickname = userInfoDTO.getNickname() != null ? userInfoDTO.getNickname() : userInfoDTO.getEmail().replaceAll("(@.*)",""); + String nickname = userInfoDTO.getNickname() != null ? userInfoDTO.getNickname() : userInfoDTO.getEmail(); user = User.builder() .email(userInfoDTO.getEmail()) .name(userInfoDTO.getName()) From 2b34d317dce2bb289b78904e44bd897682fc2121 Mon Sep 17 00:00:00 2001 From: HRiver Date: Sat, 25 May 2024 17:10:50 +0900 Subject: [PATCH 20/49] fix: oauth nickname --- .../com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java | 9 ++++++++- .../user/service/impl/CustomOAuth2UserServiceImpl.java | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java index 5def653..cdca740 100644 --- a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java +++ b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java @@ -80,11 +80,18 @@ public User toEntity() { return User.builder() .email(this.email) .name(this.name) - .nickname(this.nickname != null ? this.nickname : this.email) // 기본 닉네임 설정 + .nickname(this.nickname != null ? this.nickname : generateTempNickname()) // 기본 닉네임 설정 .imagePath(this.imagePath) .providerType(this.providerType) .providerId(this.providerId) .activeStatus(ActiveStatus.ACTIVE) .build(); } + + private String generateTempNickname() { + long unixTimestamp = System.currentTimeMillis() / 1000L; + String timestampStr = String.valueOf(unixTimestamp); + String lastSixDigits = timestampStr.length() > 6 ? timestampStr.substring(timestampStr.length() - 6) : timestampStr; + return "star" + lastSixDigits; + } } diff --git a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java index d59a574..7b3ed84 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/CustomOAuth2UserServiceImpl.java @@ -48,7 +48,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) { if (existingUser.isPresent()) { user = updateExistingUser(existingUser.get(), userInfoDTO); } else { - String nickname = userInfoDTO.getNickname() != null ? userInfoDTO.getNickname() : userInfoDTO.getEmail(); + String nickname = userInfoDTO.getNickname() != null ? userInfoDTO.getNickname() : generateTempNickname(); user = User.builder() .email(userInfoDTO.getEmail()) .name(userInfoDTO.getName()) @@ -76,6 +76,13 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) { ); } + private String generateTempNickname() { + long unixTimestamp = System.currentTimeMillis() / 1000L; + String timestampStr = String.valueOf(unixTimestamp); + String lastSixDigits = timestampStr.length() > 6 ? timestampStr.substring(timestampStr.length() - 6) : timestampStr; + return "star" + lastSixDigits; + } + private String getProviderId(OAuth2User oAuth2User, String provider) { switch (provider.toUpperCase()) { case "GOOGLE": From e13714beebb25dbd4f50378be36f87d04d104cfa Mon Sep 17 00:00:00 2001 From: choijh9023 Date: Sat, 25 May 2024 18:44:24 +0900 Subject: [PATCH 21/49] #96 StoreReviewFilter : Filter about commonFeedback --- .../starroad/config/WebSecurityConfig.java | 2 ++ .../controller/ReviewLikeController.java | 3 +- .../review/repository/ReviewRepository.java | 3 ++ .../service/impl/ReviewLikeServiceImpl.java | 1 - .../shop/controller/StoreController.java | 9 ++++-- .../shop/repository/StoreRepository.java | 6 +++- .../starroad/shop/service/StoreService.java | 2 +- .../shop/service/impl/StoreServiceImpl.java | 32 +++++++++++++------ .../shop/controller/StoreControllerTest.java | 10 +++--- 9 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java index 443e857..c796aa1 100644 --- a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java +++ b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java @@ -62,6 +62,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests(auth -> auth .requestMatchers("/user/login","/user/logout","/user/join/**","/oauth2/**","/api/token/**","/auth/**").permitAll() .requestMatchers("/api/oauth2/**").permitAll() + .requestMatchers("/review-likes/**").permitAll() + .requestMatchers("/store/**").permitAll() .requestMatchers("/user/update-profile","/user/update-password","/user/details","/user/inactive").authenticated() .anyRequest().authenticated()) .oauth2Login(oauth -> oauth diff --git a/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java b/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java index 6212f7b..cbce234 100644 --- a/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java +++ b/src/main/java/com/ssg/starroad/review/controller/ReviewLikeController.java @@ -27,8 +27,7 @@ public ResponseEntity> getUserLikes(@PathVariable String userEmail) { @PostMapping("/{reviewId}/{userEmail}") public ResponseEntity addLike(@PathVariable Long reviewId, @PathVariable String userEmail) { - User user = userRepository.findByEmail(userEmail) - .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); + ReviewLikeDTO response = reviewLikeService.addLike(userEmail, reviewId); return ResponseEntity.ok(response); } diff --git a/src/main/java/com/ssg/starroad/review/repository/ReviewRepository.java b/src/main/java/com/ssg/starroad/review/repository/ReviewRepository.java index 416bb1f..241b98d 100644 --- a/src/main/java/com/ssg/starroad/review/repository/ReviewRepository.java +++ b/src/main/java/com/ssg/starroad/review/repository/ReviewRepository.java @@ -39,4 +39,7 @@ public interface ReviewRepository extends JpaRepository { @Query("SELECT r FROM Review r WHERE r.user.id IN :userIds") Page findAllByUserIds(@Param("userIds") List userIds, Pageable pageable); + + @Query("SELECT r FROM Review r JOIN ReviewFeedback rf ON r.id = rf.review.id WHERE r.store.id = :storeId AND rf.reviewFeedbackSelection = :selection") + Page findAllWithPageByStoreIdAndReviewFeedbackSelection(@Param("storeId") Long storeId, @Param("selection") String selection, Pageable pageable); } diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java index 2e778b8..7feb7d6 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java @@ -54,7 +54,6 @@ public ReviewLikeDTO addLike(String userEmail, Long reviewId) { review.setLikeCount(review.getLikeCount() + 1); isLiked = true; } - reviewRepository.save(review); // 변경된 likeCount를 저장 return ReviewLikeDTO.builder() .isLiked(isLiked) diff --git a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java index c018009..ab1a961 100644 --- a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java +++ b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java @@ -34,10 +34,13 @@ public ResponseEntity StoreGetWithReviewById( @PathVariable Long id, @RequestParam String userEmail, @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "10") int size) { + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String filter) { try { - log.info("Received request for store ID: {}, userEmail: {}, page: {}, size: {}", id, userEmail, page, size); - StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, userEmail, page, size); + + String test = "choijh9023@naver.com"; + log.info("Received request for store ID: {}, userEmail: {}, page: {}, size: {}, filter: ", id, test, page, size,filter); + StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, test, page, size,filter); return ResponseEntity.ok(storeWithReviewDTO); } catch (RuntimeException e) { log.error("리뷰가 있는 상점을 가져오는데 실패했습니다", e); diff --git a/src/main/java/com/ssg/starroad/shop/repository/StoreRepository.java b/src/main/java/com/ssg/starroad/shop/repository/StoreRepository.java index 893cdd0..a114e69 100644 --- a/src/main/java/com/ssg/starroad/shop/repository/StoreRepository.java +++ b/src/main/java/com/ssg/starroad/shop/repository/StoreRepository.java @@ -3,6 +3,7 @@ import com.ssg.starroad.shop.entity.Store; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; @@ -15,6 +16,9 @@ public interface StoreRepository extends JpaRepository { // store 정보를 id를 기반으로 조회해서 가져오는 메서드 // 주어진 id를 기준으로 Store 엔티티를 조회하여 반환합니다. 존재하지 않을 경우 Optional.empty()를 반환합니다. Optional findById(Long id); - + Optional findByName(String name); + // shopName을 기준으로 storeType을 조회하는 새로운 메소드 + @Query("SELECT s.storeType FROM Store s WHERE s.name = :name") + String findStoreTypeByShopName(@Param("name") String name); } \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/shop/service/StoreService.java b/src/main/java/com/ssg/starroad/shop/service/StoreService.java index 4d2e6e7..d8f6395 100644 --- a/src/main/java/com/ssg/starroad/shop/service/StoreService.java +++ b/src/main/java/com/ssg/starroad/shop/service/StoreService.java @@ -10,7 +10,7 @@ public interface StoreService { List searchStoreList(Long id); - StoreWithReviewDTO findStoreWithReview(Long storeId,String userEmail, int pageNo,int pageSize); + StoreWithReviewDTO findStoreWithReview(Long storeId,String userEmail, int pageNo,int pageSize,String filter); StoreDTO findStore(Long id); String findStoreTypeByName(String name); } diff --git a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java index e139a15..7b30869 100644 --- a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java +++ b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java @@ -36,10 +36,7 @@ public class StoreServiceImpl implements StoreService { private final ReviewFeedbackService reviewFeedbackService; private final ReviewService reviewService; - @Override - public String findStoreTypeByName(String name) { - return ""; - } + private final ReviewLikeRepository reviewLikeRepository; private final UserRepository userRepository; @@ -55,19 +52,31 @@ public List searchStoreList(Long id) { return storeDTOList; } @Override - public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize) { + public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize,String filter) { + // 주어진 storeId로 스토어를 조회합니다. Store store = storeRepository.findById(storeId).orElseThrow(() -> new RuntimeException("존재하지 않는 스토어입니다.")); - + // 페이지 요청 객체를 생성합니다. Pageable pageable = PageRequest.of(pageNo, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")); - Page reviewPage = reviewRepository.findAllWithPageByStoreId(storeId, pageable); + // 스토어 ID와 필터 조건에 따라 리뷰 페이지를 조회합니다. + Page reviewPage; + if (filter == null || filter.isEmpty()) { + // 필터가 없으면 모든 리뷰를 조회합니다. + reviewPage = reviewRepository.findAllWithPageByStoreId(storeId, pageable); + } else { + // 필터가 있으면 필터 조건에 맞는 리뷰만 조회합니다. + reviewPage = reviewRepository.findAllWithPageByStoreIdAndReviewFeedbackSelection(storeId, filter, pageable); + } + // 스토어 ID로 전체 리뷰 개수를 조회합니다. Long totalReviewCount = reviewRepository.countByStoreId(storeId); + + // 특정 피드백을 받은 리뷰 개수를 조회합니다.(4개// 필수 선택지 ) long revisitCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "재방문 하고 싶어요"); long serviceSatisfactionCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "서비스가 마음에 들어요"); long reasonablePriceCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "가격이 합리적입니다"); long cleanlinessCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "매장이 청결합니다"); - // 사용자 이메일을 통해 좋아요 상태 확인 + // 사용자 이메일을 통해 사용자 ID를 조회합니다. Long userId = userRepository.findByEmail(userEmail).orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")).getId(); // List likedReviewIds = reviewLikeRepository.findLikedReviewIdsByUserId(userId); @@ -141,6 +150,11 @@ public StoreDTO findStore(Long id) { return storeDTO; } - + @Override + // 매장 이름을 이용해 매장을 조회하고 매장 유형을 반환하는 메소드 + public String findStoreTypeByName(String name) { + Store store = storeRepository.findByName(name).orElseThrow(() -> new RuntimeException("존재하지 않는 스토어입니다.")); + return store.getStoreType(); + } } \ No newline at end of file diff --git a/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java b/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java index 929ea6e..6c43487 100644 --- a/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java +++ b/src/test/java/com/ssg/starroad/shop/controller/StoreControllerTest.java @@ -22,11 +22,11 @@ public void teststoreController() { } - @Test - public void teststoreController2() { - log.info( - storeController.StoreGetWithReviewById(1L,"hklee@example.com",1,20)); - } +// @Test +// public void teststoreController2() { +// log.info( +// storeController.StoreGetWithReviewById(1L,"hklee@example.com",0,20,)); +// } @Test public void teststoreController3() { From b73617124265615667c235b46eecff4862b85c0c Mon Sep 17 00:00:00 2001 From: ekmbjh Date: Sun, 26 May 2024 20:54:36 +0900 Subject: [PATCH 22/49] =?UTF-8?q?#24=20=EB=A6=AC=EB=B7=B0=20=EC=82=AC?= =?UTF-8?q?=EC=A7=84=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../starroad/common/service/S3Uploader.java | 9 ++++++++- .../review/controller/ReviewController.java | 20 +++++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/ssg/starroad/common/service/S3Uploader.java b/src/main/java/com/ssg/starroad/common/service/S3Uploader.java index f36e8ac..164dfd8 100644 --- a/src/main/java/com/ssg/starroad/common/service/S3Uploader.java +++ b/src/main/java/com/ssg/starroad/common/service/S3Uploader.java @@ -88,7 +88,14 @@ public List upload(MultipartFile[] multipartFile, String dirName) { ObjectMetadata objectMetadata = new ObjectMetadata(); try { objectMetadata.setContentLength(file.getInputStream().available()); - objectMetadata.setContentType(file.getContentType()); +// objectMetadata.setContentType(file.getContentType()); + // 파일 타입 체크후 이미지로 변경 + if (file.getContentType().contains("image")) { + objectMetadata.setContentType("image/jpeg"); + } else { + objectMetadata.setContentType(file.getContentType()); + } + // 권한 공개 objectMetadata.setHeader("x-amz-acl", "public-read"); diff --git a/src/main/java/com/ssg/starroad/review/controller/ReviewController.java b/src/main/java/com/ssg/starroad/review/controller/ReviewController.java index f2a3395..cc04c25 100644 --- a/src/main/java/com/ssg/starroad/review/controller/ReviewController.java +++ b/src/main/java/com/ssg/starroad/review/controller/ReviewController.java @@ -75,7 +75,7 @@ public ResponseEntity getAllReviews(@RequestParam(defaultValu ResponseReviewDTO responseReviewDTO = reviewService.findAllReview(page, size); return ResponseEntity.ok(responseReviewDTO); } catch (RuntimeException e) { - return ResponseEntity.notFound().build(); + return ResponseEntity.badRequest().build(); } } @@ -98,8 +98,9 @@ public ResponseEntity getFollowingReviews(@RequestParam Long @PostMapping("/submit") public ResponseEntity submitSurvey( @RequestPart("review") String reviewStr, // JSON 문자열로 받습니다. - @RequestPart("images") List uploadedImages) throws IOException { + @RequestPart(value = "images", required = false) List uploadedImages) throws IOException { + System.out.printf("UploadedImaged : " + uploadedImages); // JSON 문자열을 ReviewDTO 객체로 변환합니다. ReviewDTO reviewDTO = new ObjectMapper().readValue(reviewStr, ReviewDTO.class); @@ -120,11 +121,15 @@ public ResponseEntity submitSurvey( .build()); Long reviewId = savedReview.getId(); - MultipartFile[] imagesArray = uploadedImages.toArray(new MultipartFile[0]); + List imageUrls = new ArrayList<>(); + if (uploadedImages != null && !uploadedImages.isEmpty()) { + imageUrls = s3Uploader.upload(uploadedImages.toArray(new MultipartFile[uploadedImages.size()]), "ssg/reviews"); + } - List imageUrls = s3Uploader.upload(imagesArray, "reviews"); - List urls = imageUrls.stream().map(url -> url.split("\\[|\\]")[1]).toList(); -// String url = imageUrls.split("\\[|\\]")[1]; + System.out.println("================================================================"); + for (String imageUrl : imageUrls) { + System.out.println(imageUrl); + } // surveyData를 합쳐서 ReviewFeedbackDTO 생성 String combinedSurveyData = reviewDTO.getCombinedSurveyData(); @@ -133,8 +138,7 @@ public ResponseEntity submitSurvey( .reviewFeedbackSelection(combinedSurveyData) .build(); - - urls.stream().map(url -> reviewImageRepository.save(ReviewImage.builder() + imageUrls.forEach(url -> reviewImageRepository.save(ReviewImage.builder() .review(savedReview) .imagePath(url) .build())); From 296d7a57f3d4311cd457b732af85dc9817848656 Mon Sep 17 00:00:00 2001 From: mariasoo <9723maria@gmail.com> Date: Mon, 27 May 2024 10:11:52 +0900 Subject: [PATCH 23/49] merge --- src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java | 2 +- .../com/ssg/starroad/user/service/impl/UserServiceImpl.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java index 21fe4fd..bd541eb 100644 --- a/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java +++ b/src/main/java/com/ssg/starroad/user/dto/OAuth2UserInfoDTO.java @@ -80,7 +80,7 @@ public User toEntity() { return User.builder() .email(this.email) .name(this.name) - .nickname(this.nickname != null ? this.nickname : this.email.split("@")[0]) // 기본 닉네임 설정 + .nickname(this.nickname != null ? this.nickname : this.email.replaceAll("(@.*)", "")) // 기본 닉네임 설정 .imagePath(this.imagePath) .providerType(this.providerType) .providerId(this.providerId) diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index c388db7..d501d69 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -138,7 +138,6 @@ public void saveProfileimg(Long id, String path) { } @Override - public boolean updatePassword(String email, String newPassword) { System.out.println("Searching for user with email: " + email); // 로그 추가 User user = userRepository.findByEmail(email).orElse(null); From 628ce81ed881349b3d5fbe0d9d5fd7caf3fe570a Mon Sep 17 00:00:00 2001 From: HRiver Date: Mon, 27 May 2024 12:10:30 +0900 Subject: [PATCH 24/49] fix: fix api id to email --- .../coupon/DTO/IssueCouponRequest.java | 12 +++--- .../coupon/controller/CouponController.java | 10 ++--- .../coupon/service/CouponHistoryService.java | 4 +- .../impl/CouponHistoryServiceImpl.java | 8 ++-- .../starroad/reward/DTO/RewardMemberDTO.java | 4 +- .../reward/controller/RewardController.java | 2 +- .../controller/RewardHistoryController.java | 6 +-- .../controller/RewardProcessController.java | 30 ++++++------- .../reward/service/RewardHistoryService.java | 2 +- .../reward/service/RewardProcessService.java | 10 ++--- .../impl/RewardHistoryServiceImpl.java | 5 ++- .../impl/RewardProcessServiceImpl.java | 17 +++++--- .../user/controller/FollowController.java | 26 ++++++------ .../user/controller/UserController.java | 24 +++++------ .../starroad/user/service/FollowService.java | 10 ++--- .../starroad/user/service/UserService.java | 8 ++-- .../user/service/impl/FollowServiceImpl.java | 17 +++++--- .../user/service/impl/UserServiceImpl.java | 16 +++---- .../repository/CouponRepositoryTest.java | 42 +++++++++---------- .../service/CouponHistorySeviceTest.java | 8 ++-- 20 files changed, 140 insertions(+), 121 deletions(-) diff --git a/src/main/java/com/ssg/starroad/coupon/DTO/IssueCouponRequest.java b/src/main/java/com/ssg/starroad/coupon/DTO/IssueCouponRequest.java index f2ac4a1..aab6036 100644 --- a/src/main/java/com/ssg/starroad/coupon/DTO/IssueCouponRequest.java +++ b/src/main/java/com/ssg/starroad/coupon/DTO/IssueCouponRequest.java @@ -3,18 +3,18 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class IssueCouponRequest { - @JsonProperty("userId") - private Long userId; + @JsonProperty("userEmail") + private String email; @JsonProperty("couponId") private Long couponId; // Getters and Setters - public Long getUserId() { - return userId; + public String getUserEmail() { + return email; } - public void setUserId(Long userId) { - this.userId = userId; + public void setUserEmail(String userId) { + this.email = userId; } public Long getCouponId() { diff --git a/src/main/java/com/ssg/starroad/coupon/controller/CouponController.java b/src/main/java/com/ssg/starroad/coupon/controller/CouponController.java index 008ac3c..5b85fe1 100644 --- a/src/main/java/com/ssg/starroad/coupon/controller/CouponController.java +++ b/src/main/java/com/ssg/starroad/coupon/controller/CouponController.java @@ -19,17 +19,17 @@ public class CouponController { @PostMapping("/issue") public ResponseEntity issueCoupon(@RequestBody IssueCouponRequest request) { - if (request.getUserId() == null || request.getCouponId() == null) { + if (request.getUserEmail() == null || request.getCouponId() == null) { return ResponseEntity.badRequest().body(0); } - int rate = couponHistoryService.CouponUserAdd(request.getUserId(), request.getCouponId()); + int rate = couponHistoryService.CouponUserAdd(request.getUserEmail(), request.getCouponId()); return ResponseEntity.ok(rate); } - @GetMapping("/{userId}/coupon/list") - public ResponseEntity> getUserCoupons(@PathVariable Long userId) { - List coupons = couponHistoryService.CouponsUserList(userId); + @GetMapping("/{email}/coupon/list") + public ResponseEntity> getUserCoupons(@PathVariable String email) { + List coupons = couponHistoryService.CouponsUserList(email); if (coupons.isEmpty()) { return ResponseEntity.noContent().build(); // 내용이 없을 경우 No Content (204) 반환 } diff --git a/src/main/java/com/ssg/starroad/coupon/service/CouponHistoryService.java b/src/main/java/com/ssg/starroad/coupon/service/CouponHistoryService.java index 558566b..df905fa 100644 --- a/src/main/java/com/ssg/starroad/coupon/service/CouponHistoryService.java +++ b/src/main/java/com/ssg/starroad/coupon/service/CouponHistoryService.java @@ -7,7 +7,7 @@ public interface CouponHistoryService { - int CouponUserAdd(Long userID, Long couponID); + int CouponUserAdd(String userEmail, Long couponID); @@ -19,5 +19,5 @@ public interface CouponHistoryService { - List CouponsUserList(Long userID); + List CouponsUserList(String email); } diff --git a/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java b/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java index 3f1b74c..af2a846 100644 --- a/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java +++ b/src/main/java/com/ssg/starroad/coupon/service/impl/CouponHistoryServiceImpl.java @@ -34,17 +34,18 @@ public class CouponHistoryServiceImpl implements CouponHistoryService { private final RewardProcessRepository rewardProcessRepository; @Override - public int CouponUserAdd(Long userID, Long couponID) { + public int CouponUserAdd(String email, Long couponID) { Coupon coupon = couponRepository.findById(couponID).orElseThrow(); int max = coupon.getMaxAmount(); int min = coupon.getMinAmount(); int rate = ThreadLocalRandom.current().nextInt(min, max + 1); CouponHistory couponHistory = - new CouponHistory(null, userRepository.findById(userID).orElseThrow(), + new CouponHistory(null, userRepository.findByEmail(email).orElseThrow(), couponID,false, LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY)),rate); couponHistoryRepository.save(couponHistory); + Long userID = userRepository.findByEmail(email).orElseThrow().getId(); RewardProcess rewardProcess =rewardProcessRepository.findById(userID).orElseThrow(); rewardProcess.setIssueStatus(true); rewardProcessRepository.save(rewardProcess); @@ -76,7 +77,8 @@ public boolean CouponUsageModify(Long couponHistoryId) { } @Override - public List CouponsUserList(Long userID) { + public List CouponsUserList(String email) { + Long userID = userRepository.findByEmail(email).orElseThrow().getId(); List coupons = couponHistoryRepositoryCustom.findCouponsByUserId(userID); LocalDate today = LocalDate.now(); diff --git a/src/main/java/com/ssg/starroad/reward/DTO/RewardMemberDTO.java b/src/main/java/com/ssg/starroad/reward/DTO/RewardMemberDTO.java index c7c4e37..c733d0f 100644 --- a/src/main/java/com/ssg/starroad/reward/DTO/RewardMemberDTO.java +++ b/src/main/java/com/ssg/starroad/reward/DTO/RewardMemberDTO.java @@ -9,8 +9,8 @@ @AllArgsConstructor @ToString public class RewardMemberDTO { - @JsonProperty("member_id") - private Long memberId; + @JsonProperty("member_email") + private String email; @JsonProperty("reward_id") private Long rewardId; diff --git a/src/main/java/com/ssg/starroad/reward/controller/RewardController.java b/src/main/java/com/ssg/starroad/reward/controller/RewardController.java index 26dc7da..1c5dc21 100644 --- a/src/main/java/com/ssg/starroad/reward/controller/RewardController.java +++ b/src/main/java/com/ssg/starroad/reward/controller/RewardController.java @@ -29,6 +29,6 @@ public ResponseEntity RewardList (){ @PostMapping("/obtain") public ResponseEntity Rewardobtain(@RequestBody RewardMemberDTO rewardmemberDTO){ rewardHistoryService.addReward(rewardmemberDTO); - return ResponseEntity.ok("Reward issued successfully to user ID " + rewardmemberDTO.getMemberId()); + return ResponseEntity.ok("Reward issued successfully to user email " + rewardmemberDTO.getEmail()); } } diff --git a/src/main/java/com/ssg/starroad/reward/controller/RewardHistoryController.java b/src/main/java/com/ssg/starroad/reward/controller/RewardHistoryController.java index be399f2..aeec028 100644 --- a/src/main/java/com/ssg/starroad/reward/controller/RewardHistoryController.java +++ b/src/main/java/com/ssg/starroad/reward/controller/RewardHistoryController.java @@ -19,9 +19,9 @@ public class RewardHistoryController { private final RewardHistoryService rewardHistoryService; - @GetMapping("/user/{userId}/list") - public ResponseEntity> getRewardHistory(@PathVariable("userId") Long userId) { - List rewardHistoryDTOList = rewardHistoryService.getRewardHistory(userId); + @GetMapping("/user/{email}/list") + public ResponseEntity> getRewardHistory(@PathVariable("email") String email) { + List rewardHistoryDTOList = rewardHistoryService.getRewardHistory(email); if (rewardHistoryDTOList.isEmpty()) { return ResponseEntity.noContent().build(); // 내용이 없을 경우 No Content (204) 반환 } diff --git a/src/main/java/com/ssg/starroad/reward/controller/RewardProcessController.java b/src/main/java/com/ssg/starroad/reward/controller/RewardProcessController.java index 0427c70..d7385a9 100644 --- a/src/main/java/com/ssg/starroad/reward/controller/RewardProcessController.java +++ b/src/main/java/com/ssg/starroad/reward/controller/RewardProcessController.java @@ -15,10 +15,10 @@ public class RewardProcessController { private final RewardProcessService rewardProcessService; - @GetMapping("/{userId}/start") - public ResponseEntity startRewardProcess(@PathVariable Long userId) { + @GetMapping("/{email}/start") + public ResponseEntity startRewardProcess(@PathVariable String email) { // 여기서 userId와 DTO를 이용하여 처리 로직 호출 - rewardProcessService.startRewardProcess(userId); + rewardProcessService.startRewardProcess(email); return ResponseEntity.ok().build(); } @@ -32,38 +32,38 @@ public ResponseEntity updateCouponCount(@PathVariable Long userId) { } } - @PatchMapping("/{userId}/review") - public ResponseEntity updateReviewCount(@PathVariable Long userId) { + @PatchMapping("/{email}/review") + public ResponseEntity updateReviewCount(@PathVariable String email) { try { - rewardProcessService.updateReviewCount(userId); + rewardProcessService.updateReviewCount(email); return ResponseEntity.ok().build(); } catch (Exception e) { return ResponseEntity.badRequest().body("Error updating review count: " + e.getMessage()); } } - @PatchMapping("/{userId}/completed") - public ResponseEntity updateStatus(@PathVariable Long userId) { + @PatchMapping("/{email}/completed") + public ResponseEntity updateStatus(@PathVariable String email) { try { - rewardProcessService.updateStatus(userId); + rewardProcessService.updateStatus(email); return ResponseEntity.ok().build(); } catch (Exception e) { return ResponseEntity.badRequest().body("Error updating status: " + e.getMessage()); } } - @PatchMapping("/{userId}/new") - public ResponseEntity resetStatus(@PathVariable Long userId) { + @PatchMapping("/{email}/new") + public ResponseEntity resetStatus(@PathVariable String email) { try { - rewardProcessService.resetStatus(userId); + rewardProcessService.resetStatus(email); return ResponseEntity.ok().build(); } catch (Exception e) { return ResponseEntity.badRequest().body("Error updating status: " + e.getMessage()); } } - @GetMapping("/{userId}/get") - public ResponseEntity getProcess(@PathVariable("userId") Long userId){ - RewardProcessDTO rewardProcessDTO=rewardProcessService.getProcess(userId); + @GetMapping("/{email}/get") + public ResponseEntity getProcess(@PathVariable("email") String email){ + RewardProcessDTO rewardProcessDTO=rewardProcessService.getProcess(email); if (rewardProcessDTO==null) { return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/ssg/starroad/reward/service/RewardHistoryService.java b/src/main/java/com/ssg/starroad/reward/service/RewardHistoryService.java index 2b386d5..ed64b86 100644 --- a/src/main/java/com/ssg/starroad/reward/service/RewardHistoryService.java +++ b/src/main/java/com/ssg/starroad/reward/service/RewardHistoryService.java @@ -7,5 +7,5 @@ public interface RewardHistoryService { public void addReward(RewardMemberDTO rewardmemberDTO); - public List getRewardHistory(Long userId); + public List getRewardHistory(String email); } diff --git a/src/main/java/com/ssg/starroad/reward/service/RewardProcessService.java b/src/main/java/com/ssg/starroad/reward/service/RewardProcessService.java index d3276bc..2b10877 100644 --- a/src/main/java/com/ssg/starroad/reward/service/RewardProcessService.java +++ b/src/main/java/com/ssg/starroad/reward/service/RewardProcessService.java @@ -3,16 +3,16 @@ import com.ssg.starroad.reward.DTO.RewardProcessDTO; public interface RewardProcessService { - public void startRewardProcess(Long userId); + public void startRewardProcess(String email); public void updateCouponCount(Long userId); - public void updateReviewCount(Long userId); + public void updateReviewCount(String email); - public void updateStatus(Long userId); - public void resetStatus(Long userId); + public void updateStatus(String email); + public void resetStatus(String email); - public RewardProcessDTO getProcess(Long userId); + public RewardProcessDTO getProcess(String email); } diff --git a/src/main/java/com/ssg/starroad/reward/service/impl/RewardHistoryServiceImpl.java b/src/main/java/com/ssg/starroad/reward/service/impl/RewardHistoryServiceImpl.java index bb9ea46..d94c7ad 100644 --- a/src/main/java/com/ssg/starroad/reward/service/impl/RewardHistoryServiceImpl.java +++ b/src/main/java/com/ssg/starroad/reward/service/impl/RewardHistoryServiceImpl.java @@ -26,7 +26,7 @@ public class RewardHistoryServiceImpl implements RewardHistoryService { @Override public void addReward(RewardMemberDTO rewardmemberDTO) { - Optional user=userRepository.findById(rewardmemberDTO.getMemberId()); + Optional user=userRepository.findByEmail(rewardmemberDTO.getEmail()); user.ifPresent(value -> rewardHistoryRepository.save(RewardHistory.builder() .rewardId(rewardmemberDTO.getRewardId()) @@ -34,7 +34,8 @@ public void addReward(RewardMemberDTO rewardmemberDTO) { } @Override - public List getRewardHistory(Long userId) { + public List getRewardHistory(String email) { + Long userId = userRepository.findByEmail(email).orElseThrow().getId(); List rewardCounts = rewardHistoryRepositoryCustom.countRewardHistoryByUserId(userId); List result = new ArrayList<>(); diff --git a/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java b/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java index 4319158..b0dabda 100644 --- a/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java +++ b/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java @@ -4,6 +4,7 @@ import com.ssg.starroad.reward.entity.RewardProcess; import com.ssg.starroad.reward.repository.RewardProcessRepository; import com.ssg.starroad.reward.service.RewardProcessService; +import com.ssg.starroad.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; @@ -20,11 +21,13 @@ public class RewardProcessServiceImpl implements RewardProcessService { private final RewardProcessRepository rewardProcessRepository; + private final UserRepository userRepository; private final ModelMapper modelMapper; @Override @Transactional - public void startRewardProcess(Long userId) { + public void startRewardProcess(String email) { + Long userId = userRepository.findByEmail(email).orElseThrow().getId(); RewardProcess rewardProcess = rewardProcessRepository.findById(userId) .orElse(new RewardProcess(userId)); // 존재하지 않는 경우 새 객체 생성 @@ -46,7 +49,8 @@ public void updateCouponCount(Long userId) { @Override @Transactional - public void updateReviewCount(Long userId) { + public void updateReviewCount(String email) { + Long userId = userRepository.findByEmail(email).orElseThrow().getId(); RewardProcess rewardProcess = rewardProcessRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("Invalid user ID: " + userId)); rewardProcess.setReviewCount(rewardProcess.getReviewCount() + 1); @@ -55,7 +59,8 @@ public void updateReviewCount(Long userId) { @Override @Transactional - public void updateStatus(Long userId) { + public void updateStatus(String email) { + Long userId = userRepository.findByEmail(email).orElseThrow().getId(); RewardProcess rewardProcess = rewardProcessRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("Invalid user ID: " + userId)); rewardProcess.setRewardStatus(true); @@ -63,7 +68,8 @@ public void updateStatus(Long userId) { } @Override - public void resetStatus(Long userId) { + public void resetStatus(String email) { + Long userId = userRepository.findByEmail(email).orElseThrow().getId(); RewardProcess rewardProcess = rewardProcessRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("Invalid user ID: " + userId)); rewardProcess.setIssueStatus(false); @@ -73,7 +79,8 @@ public void resetStatus(Long userId) { @Override @Transactional - public RewardProcessDTO getProcess(Long userId){ + public RewardProcessDTO getProcess(String email){ + Long userId = userRepository.findByEmail(email).orElseThrow().getId(); RewardProcess rewardProcess = rewardProcessRepository.findById(userId) .orElseThrow(() -> new IllegalArgumentException("Invalid user ID: " + userId)); if(Objects.equals(rewardProcess.getExpiredAt(), LocalDate.now())) diff --git a/src/main/java/com/ssg/starroad/user/controller/FollowController.java b/src/main/java/com/ssg/starroad/user/controller/FollowController.java index 20c3aed..4920acc 100644 --- a/src/main/java/com/ssg/starroad/user/controller/FollowController.java +++ b/src/main/java/com/ssg/starroad/user/controller/FollowController.java @@ -17,29 +17,29 @@ public class FollowController { private final FollowService followService; - @GetMapping("/mycount/{userId}") - public ResponseEntity getCountByFromUserId(@PathVariable Long userId) { - long followerCount = followService.getCountByFromUserId(userId); - long followingCount = followService.getCountByToUserId(userId); + @GetMapping("/mycount/{email}") + public ResponseEntity getCountByFromUserId(@PathVariable String email) { + long followerCount = followService.getCountByFromUserId(email); + long followingCount = followService.getCountByToUserId(email); FollowCountDTO followCountDTO = new FollowCountDTO(followerCount, followingCount); return ResponseEntity.ok(followCountDTO); } - @GetMapping("/from/{userId}") - public ResponseEntity> getFollowsByFromUserId(@PathVariable Long userId) { - List follows = followService.getFollowsByFromUserId(userId); + @GetMapping("/from/{email}") + public ResponseEntity> getFollowsByFromUserId(@PathVariable String email) { + List follows = followService.getFollowsByFromUserId(email); return ResponseEntity.ok(follows); } - @GetMapping("/to/{userId}") - public ResponseEntity> getFollowsByToUserId(@PathVariable Long userId) { - List follows = followService.getFollowsByToUserId(userId); + @GetMapping("/to/{email}") + public ResponseEntity> getFollowsByToUserId(@PathVariable String email) { + List follows = followService.getFollowsByToUserId(email); return ResponseEntity.ok(follows); } - @DeleteMapping("/{id}/deletefrom/{userId}") - public ResponseEntity deleteFollowingUser(@PathVariable Long userId, @PathVariable Long id) { - followService.deleteFollowingUser(userId, id); + @DeleteMapping("/{id}/deletefrom/{email}") + public ResponseEntity deleteFollowingUser(@PathVariable String email, @PathVariable Long id) { + followService.deleteFollowingUser(email, id); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/com/ssg/starroad/user/controller/UserController.java b/src/main/java/com/ssg/starroad/user/controller/UserController.java index f011c0a..684cf9d 100644 --- a/src/main/java/com/ssg/starroad/user/controller/UserController.java +++ b/src/main/java/com/ssg/starroad/user/controller/UserController.java @@ -156,35 +156,35 @@ public ResponseEntity deactivateUser(@RequestBody Map re - @GetMapping("/mypage/{userId}") - public ResponseEntity getMypage(@PathVariable Long userId) { - MypageDTO mypageDTO=userService.getMypage(userId); + @GetMapping("/mypage/{email}") + public ResponseEntity getMypage(@PathVariable String email) { + MypageDTO mypageDTO=userService.getMypage(email); if (mypageDTO==null) { return ResponseEntity.noContent().build(); } return ResponseEntity.ok(mypageDTO); } - @PostMapping("/profile/upload/img/{userId}") - public ResponseEntity uploadImg(@PathVariable Long userId, @RequestParam("file") MultipartFile uploadImg) { + @PostMapping("/profile/upload/img/{email}") + public ResponseEntity uploadImg(@PathVariable String email, @RequestParam("file") MultipartFile uploadImg) { String path = s3Uploader.upload(uploadImg,"ssg/user/profile"); - userService.saveProfileimg(userId,path); + userService.saveProfileimg(email,path); return ResponseEntity.ok().build(); } - @GetMapping("/profile/get/img/{userId}") - public ResponseEntity getImg(@PathVariable Long userId) + @GetMapping("/profile/get/img/{email}") + public ResponseEntity getImg(@PathVariable String email) { - String path =userService.getProfileimg(userId); + String path =userService.getProfileimg(email); if (path==null) { return ResponseEntity.noContent().build(); } return ResponseEntity.ok(path.trim()); } - @DeleteMapping("/profile/delete/img/{userId}") - public ResponseEntity deleteImg(@PathVariable Long userId){ - userService.deleteProfileimg(userId); + @DeleteMapping("/profile/delete/img/{email}") + public ResponseEntity deleteImg(@PathVariable String email){ + userService.deleteProfileimg(email); return ResponseEntity.ok().build(); } } diff --git a/src/main/java/com/ssg/starroad/user/service/FollowService.java b/src/main/java/com/ssg/starroad/user/service/FollowService.java index a843e3f..3886d2d 100644 --- a/src/main/java/com/ssg/starroad/user/service/FollowService.java +++ b/src/main/java/com/ssg/starroad/user/service/FollowService.java @@ -6,9 +6,9 @@ import java.util.List; public interface FollowService { - public long getCountByFromUserId(Long userId); - public long getCountByToUserId(Long userId); - public List getFollowsByToUserId(Long toUserId); - public List getFollowsByFromUserId(Long fromUserId); - public void deleteFollowingUser(Long userid,Long toUserId); + public long getCountByFromUserId(String email); + public long getCountByToUserId(String email); + public List getFollowsByToUserId(String email); + public List getFollowsByFromUserId(String email); + public void deleteFollowingUser(String email,Long toUserId); } diff --git a/src/main/java/com/ssg/starroad/user/service/UserService.java b/src/main/java/com/ssg/starroad/user/service/UserService.java index 2fad5f6..22b8cbd 100644 --- a/src/main/java/com/ssg/starroad/user/service/UserService.java +++ b/src/main/java/com/ssg/starroad/user/service/UserService.java @@ -31,9 +31,9 @@ public interface UserService extends UserDetailsService { void inactiveUser(String email); Optional findNicknameById(Long id); - public MypageDTO getMypage(Long id); - public void saveProfileimg(Long id, String path); - public String getProfileimg(Long id); - public void deleteProfileimg(Long id); + public MypageDTO getMypage(String email); + public void saveProfileimg(String email, String path); + public String getProfileimg(String email); + public void deleteProfileimg(String email); } diff --git a/src/main/java/com/ssg/starroad/user/service/impl/FollowServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/FollowServiceImpl.java index 440f4dd..db8183b 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/FollowServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/FollowServiceImpl.java @@ -4,6 +4,7 @@ import com.ssg.starroad.user.entity.Follow; import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.repository.FollowRepository; +import com.ssg.starroad.user.repository.UserRepository; import com.ssg.starroad.user.service.FollowService; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @@ -17,22 +18,27 @@ public class FollowServiceImpl implements FollowService { private final FollowRepository followRepository; + private final UserRepository userRepository; - public long getCountByFromUserId(Long userId) { + public long getCountByFromUserId(String email) { + Long userId = userRepository.findByEmail(email).orElseThrow().getId(); return followRepository.countByFromUserId(userId); } - public long getCountByToUserId(Long userId) { + public long getCountByToUserId(String email) { + Long userId = userRepository.findByEmail(email).orElseThrow().getId(); return followRepository.countByToUserId(userId); } - public List getFollowsByFromUserId(Long fromUserId) { + public List getFollowsByFromUserId(String email) { + Long fromUserId = userRepository.findByEmail(email).orElseThrow().getId(); List follows = followRepository.findToUsersByFromUserId(fromUserId); return follows.stream() .map(this::convertToDTO) .collect(Collectors.toList()); } - public List getFollowsByToUserId(Long toUserId) { + public List getFollowsByToUserId(String email) { + Long toUserId = userRepository.findByEmail(email).orElseThrow().getId(); List follows = followRepository.findFromUsersByToUserId(toUserId); return follows.stream() .map(this::convertToDTO) @@ -50,7 +56,8 @@ private FollowDTO convertToDTO(User user) { @Transactional @Override - public void deleteFollowingUser(Long userid,Long toUserId) { + public void deleteFollowingUser(String email,Long toUserId) { + Long userid = userRepository.findByEmail(email).orElseThrow().getId(); followRepository.deleteByFromUserIdAndToUserId(userid, toUserId); } diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index 8310993..e97fc09 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -128,13 +128,15 @@ public Optional findNicknameById(Long id) { return userOptional.map(User::getNickname); } - public MypageDTO getMypage(Long id) { + public MypageDTO getMypage(String email) { + Long id = userRepository.findByEmail(email).orElseThrow().getId(); return userRepository.findById(id).map(info -> modelMapper.map(info, MypageDTO.class)).orElse(null); } @Override - public void saveProfileimg(Long id, String path) { - User user=userRepository.findById(id).orElseThrow(); + public void saveProfileimg(String email, String path) { + + User user=userRepository.findByEmail(email).orElseThrow(); user.setProfileimgPath(path); userRepository.save(user); } @@ -180,14 +182,14 @@ public void inactiveUser(String email) { user.changeActiveStatus(ActiveStatus.INACTIVE); } - public String getProfileimg(Long id) { - User user =userRepository.findById(id).orElseThrow(); + public String getProfileimg(String email) { + User user =userRepository.findByEmail(email).orElseThrow(); return user.getImagePath(); } @Override - public void deleteProfileimg(Long id) { - User user =userRepository.findById(id).orElseThrow(); + public void deleteProfileimg(String email) { + User user =userRepository.findByEmail(email).orElseThrow(); user.setProfileimgPath(""); userRepository.save(user); } diff --git a/src/test/java/com/ssg/starroad/coupon/repository/CouponRepositoryTest.java b/src/test/java/com/ssg/starroad/coupon/repository/CouponRepositoryTest.java index 952ae3f..7cc2d01 100644 --- a/src/test/java/com/ssg/starroad/coupon/repository/CouponRepositoryTest.java +++ b/src/test/java/com/ssg/starroad/coupon/repository/CouponRepositoryTest.java @@ -39,29 +39,29 @@ public class CouponRepositoryTest { @Autowired private UserRepository userRepository; +} +// @Test +// public void testSaveCoupon() { +// // 쿠폰 엔티티 생성 +// Coupon coupon = new Coupon(null, "Complex2", "Food", "Summer Sale", 1000, 10, 5000, 10000, "Active", LocalDate.now().plusDays(7)); +// +// // 쿠폰 저장 +// Coupon savedCoupon = couponRepository.save(coupon); +// +// // 로그 출력 +// log.info("Saved Coupon: {}", savedCoupon); +// +// // 검증 +// assertNotNull(savedCoupon); +// assertNotNull(savedCoupon.getId()); +// } - @Test - public void testSaveCoupon() { - // 쿠폰 엔티티 생성 - Coupon coupon = new Coupon(null, "Complex2", "Food", "Summer Sale", 1000, 10, 5000, 10000, "Active", LocalDate.now().plusDays(7)); - - // 쿠폰 저장 - Coupon savedCoupon = couponRepository.save(coupon); - - // 로그 출력 - log.info("Saved Coupon: {}", savedCoupon); - - // 검증 - assertNotNull(savedCoupon); - assertNotNull(savedCoupon.getId()); - } - - @Test - public void testIssueCoupon() { - couponHistoryService.CouponUserAdd(4L, 3L); +// @Test +// public void testIssueCoupon() { +// couponHistoryService.CouponUserAdd(4L, 3L); // log.info("check issue coupon: {}", couponHistoryRepository.findById(1L)); - } -} +// } +//} // @Test // public void testfindByUserId(){ //// Long userId = 1L; diff --git a/src/test/java/com/ssg/starroad/coupon/service/CouponHistorySeviceTest.java b/src/test/java/com/ssg/starroad/coupon/service/CouponHistorySeviceTest.java index 0c79902..5bd9d40 100644 --- a/src/test/java/com/ssg/starroad/coupon/service/CouponHistorySeviceTest.java +++ b/src/test/java/com/ssg/starroad/coupon/service/CouponHistorySeviceTest.java @@ -34,9 +34,9 @@ public void testupdateCouponUsage(){ couponHistoryService.CouponUsageModify(2L); log.info("coupon status: {} ",couponHistoryRepository.findById(2L)); } - @Test - public void testgetCouponsByUserID(){ - log.info("user coupons:{}",couponHistoryService.CouponsUserList(4L)); - } +// @Test +// public void testgetCouponsByUserID(){ +// log.info("user coupons:{}",couponHistoryService.CouponsUserList(4L)); +// } } From 7d0dddecb1097de04b6fb3e5ec5bec77f03fdc3b Mon Sep 17 00:00:00 2001 From: HRiver Date: Mon, 27 May 2024 13:33:19 +0900 Subject: [PATCH 25/49] fix: fix api id to email --- src/main/java/com/ssg/starroad/user/dto/MypageDTO.java | 2 -- src/main/java/com/ssg/starroad/user/entity/User.java | 2 ++ .../starroad/user/service/impl/UserServiceImpl.java | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/ssg/starroad/user/dto/MypageDTO.java b/src/main/java/com/ssg/starroad/user/dto/MypageDTO.java index af7c5e6..c32d0d4 100644 --- a/src/main/java/com/ssg/starroad/user/dto/MypageDTO.java +++ b/src/main/java/com/ssg/starroad/user/dto/MypageDTO.java @@ -13,7 +13,5 @@ public class MypageDTO { private String profileImg; private int reviewExp; private String name; - private int following; - private int follower; private int point; } diff --git a/src/main/java/com/ssg/starroad/user/entity/User.java b/src/main/java/com/ssg/starroad/user/entity/User.java index 6a84435..015cf98 100644 --- a/src/main/java/com/ssg/starroad/user/entity/User.java +++ b/src/main/java/com/ssg/starroad/user/entity/User.java @@ -93,6 +93,8 @@ public String getName() { return this.providerId; // providerId를 반환 } + public String getUserName() {return this.name;} + // 제네릭 타입 A를 사용하여, OAuth2User 인터페이스에서 // 다양한 타입의 사용자 속성을 안전하게 가져오도록 설계(필수) // String email = user.getAttribute("email")과 같이 사용할 때 diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index e97fc09..73fdf80 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -129,8 +129,14 @@ public Optional findNicknameById(Long id) { } public MypageDTO getMypage(String email) { - Long id = userRepository.findByEmail(email).orElseThrow().getId(); - return userRepository.findById(id).map(info -> modelMapper.map(info, MypageDTO.class)).orElse(null); + User user = userRepository.findByEmail(email).orElseThrow(); + return MypageDTO.builder() + .nickname(user.getNickname()) + .profileImg(user.getImagePath()) + .reviewExp(user.getReviewExp()) + .name(user.getUserName()) + .point(user.getPoint()) + .build(); } @Override From 8ba5a1fe4763f7939e6b2a4a297e52b7dd581a66 Mon Sep 17 00:00:00 2001 From: HRiver Date: Mon, 27 May 2024 15:29:42 +0900 Subject: [PATCH 26/49] fix: fix api id to email --- .../ssg/starroad/config/jwt/TokenProvider.java | 17 ++++++++++++----- .../user/controller/UserController.java | 10 ++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java index d97d259..868ea6c 100644 --- a/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java +++ b/src/main/java/com/ssg/starroad/config/jwt/TokenProvider.java @@ -3,6 +3,7 @@ // JwtProperties 클래스에서 설정된 값을 사용하여 토큰을 관리 import com.ssg.starroad.user.entity.User; +import io.jsonwebtoken.Jws; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -56,16 +57,22 @@ public String createToken(Date expiry, User user) { } // 제공된 토큰이 유효한지 검사. 토큰의 서명 검증. 파싱 중 발생할 수 있는 예외 처리 - // 예외가 발생하면 false 그렇지 않으면 true +// 예외가 발생하면 false 그렇지 않으면 true public boolean validToken(String token) { try { SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtProperties.getSecretKey())); - Jwts.parserBuilder() - .setSigningKey(key)// 비밀값으로 복호화 + Jws claimsJws = Jwts.parserBuilder() + .setSigningKey(key) .build() .parseClaimsJws(token); - return true; - } catch (Exception e) { // 복호화 과정에서 에러가 나면 유효하지 않은 토큰 + + Claims claims = claimsJws.getBody(); + Date expiration = claims.getExpiration(); + System.out.println("Token expiration time: " + expiration); + + return !expiration.before(new Date()); + } catch (Exception e) { + System.out.println("Invalid token: " + e.getMessage()); return false; } } diff --git a/src/main/java/com/ssg/starroad/user/controller/UserController.java b/src/main/java/com/ssg/starroad/user/controller/UserController.java index 684cf9d..1bcc5a7 100644 --- a/src/main/java/com/ssg/starroad/user/controller/UserController.java +++ b/src/main/java/com/ssg/starroad/user/controller/UserController.java @@ -10,6 +10,7 @@ import com.ssg.starroad.user.dto.MypageDTO; import com.ssg.starroad.user.service.UserService; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -133,9 +134,18 @@ public ResponseEntity logout(@RequestBody LogoutRequest logoutRequest, HttpSe String email = logoutRequest.getEmail(); String accessToken = logoutRequest.getAccessToken(); + // 로그 추가 + System.out.println("Logout request received"); + System.out.println("Email: " + email); + System.out.println("Access token: " + accessToken); + if (tokenProvider.validToken(accessToken)) { // 이메일과 토큰으로 사용자 인증 및 토큰 무효화 로직 추가 refreshTokenRepository.deleteByUserEmail(email); + System.out.println("Token is valid. Proceeding with logout."); + }else { + System.out.println("Invalid token."); + return ResponseEntity.status(HttpServletResponse.SC_UNAUTHORIZED).body("Invalid token"); } // 세션 무효화 request.getSession().invalidate(); From 522cd5170d07df5e760e8db351a68c4b9e3fa4bb Mon Sep 17 00:00:00 2001 From: HRiver Date: Mon, 27 May 2024 15:46:56 +0900 Subject: [PATCH 27/49] fix: delete cors other files --- .../com/ssg/starroad/config/WebConfigDev.java | 40 +++++++++---------- .../ssg/starroad/config/WebConfigProd.java | 40 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/ssg/starroad/config/WebConfigDev.java b/src/main/java/com/ssg/starroad/config/WebConfigDev.java index 9ffa676..a9ae090 100644 --- a/src/main/java/com/ssg/starroad/config/WebConfigDev.java +++ b/src/main/java/com/ssg/starroad/config/WebConfigDev.java @@ -1,20 +1,20 @@ -package com.ssg.starroad.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Profile("development") -@Configuration -public class WebConfigDev implements WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins("http://localhost:8080") - .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") - .allowedHeaders("*") - .allowCredentials(true) - .exposedHeaders("Authorization", "refreshToken"); // 서버의 응답에서 노출할 헤더 추가;; - } -} +//package com.ssg.starroad.config; +// +//import org.springframework.context.annotation.Configuration; +//import org.springframework.context.annotation.Profile; +//import org.springframework.web.servlet.config.annotation.CorsRegistry; +//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +// +//@Profile("development") +//@Configuration +//public class WebConfigDev implements WebMvcConfigurer { +// @Override +// public void addCorsMappings(CorsRegistry registry) { +// registry.addMapping("/**") +// .allowedOrigins("http://localhost:8080") +// .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") +// .allowedHeaders("*") +// .allowCredentials(true) +// .exposedHeaders("Authorization", "refreshToken"); // 서버의 응답에서 노출할 헤더 추가;; +// } +//} diff --git a/src/main/java/com/ssg/starroad/config/WebConfigProd.java b/src/main/java/com/ssg/starroad/config/WebConfigProd.java index 2c30933..58c26a9 100644 --- a/src/main/java/com/ssg/starroad/config/WebConfigProd.java +++ b/src/main/java/com/ssg/starroad/config/WebConfigProd.java @@ -1,20 +1,20 @@ -package com.ssg.starroad.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Profile; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Profile("production") -@Configuration -public class WebConfigProd implements WebMvcConfigurer { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/**") - .allowedOrigins("http://10.0.15.6:8080", "http://223.130.155.196:8080") - .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") - .allowedHeaders("*") - .allowCredentials(true) - .exposedHeaders("Authorization", "refreshToken"); // 서버의 응답에서 노출할 헤더 추가; - } -} +//package com.ssg.starroad.config; +// +//import org.springframework.context.annotation.Configuration; +//import org.springframework.context.annotation.Profile; +//import org.springframework.web.servlet.config.annotation.CorsRegistry; +//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +// +//@Profile("production") +//@Configuration +//public class WebConfigProd implements WebMvcConfigurer { +// @Override +// public void addCorsMappings(CorsRegistry registry) { +// registry.addMapping("/**") +// .allowedOrigins("http://10.0.15.6:8080", "http://223.130.155.196:8080") +// .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") +// .allowedHeaders("*") +// .allowCredentials(true) +// .exposedHeaders("Authorization", "refreshToken"); // 서버의 응답에서 노출할 헤더 추가; +// } +//} From e0d7faced43d6f912966c1152816792826b4f9d1 Mon Sep 17 00:00:00 2001 From: HRiver Date: Mon, 27 May 2024 15:55:27 +0900 Subject: [PATCH 28/49] fix: change port --- src/main/java/com/ssg/starroad/config/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/config/CorsConfig.java b/src/main/java/com/ssg/starroad/config/CorsConfig.java index 766079f..6cab4b0 100644 --- a/src/main/java/com/ssg/starroad/config/CorsConfig.java +++ b/src/main/java/com/ssg/starroad/config/CorsConfig.java @@ -13,7 +13,7 @@ public UrlBasedCorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.addAllowedOriginPattern("http://223.130.155.196:8080"); // 프로덕션용 + config.addAllowedOriginPattern("http://223.130.155.196"); // 프로덕션용 config.addAllowedOriginPattern("http://localhost:8080"); // Vue 애플리케이션 주소 추가 config.addAllowedHeader("*"); // 서버가 받을 수 있는 모든 헤더를 정의 From 4254cf1a7b8904b77e4d691215887d7241c3dbed Mon Sep 17 00:00:00 2001 From: HRiver Date: Mon, 27 May 2024 16:02:17 +0900 Subject: [PATCH 29/49] fix: change port --- src/main/java/com/ssg/starroad/config/CorsConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/config/CorsConfig.java b/src/main/java/com/ssg/starroad/config/CorsConfig.java index 6cab4b0..766079f 100644 --- a/src/main/java/com/ssg/starroad/config/CorsConfig.java +++ b/src/main/java/com/ssg/starroad/config/CorsConfig.java @@ -13,7 +13,7 @@ public UrlBasedCorsConfigurationSource corsConfigurationSource() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.addAllowedOriginPattern("http://223.130.155.196"); // 프로덕션용 + config.addAllowedOriginPattern("http://223.130.155.196:8080"); // 프로덕션용 config.addAllowedOriginPattern("http://localhost:8080"); // Vue 애플리케이션 주소 추가 config.addAllowedHeader("*"); // 서버가 받을 수 있는 모든 헤더를 정의 From f7757a31b6bae3024599fa34b85db3e023adf24b Mon Sep 17 00:00:00 2001 From: HRiver Date: Mon, 27 May 2024 16:10:32 +0900 Subject: [PATCH 30/49] fix: add url at config --- src/main/java/com/ssg/starroad/config/WebSecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java index c796aa1..aa6b9c6 100644 --- a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java +++ b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java @@ -60,7 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement(sessionManagementConfig -> sessionManagementConfig .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/user/login","/user/logout","/user/join/**","/oauth2/**","/api/token/**","/auth/**").permitAll() + .requestMatchers("/user/login","/login/**","/user/logout","/user/join/**","/oauth2/**","/api/token/**","/auth/**").permitAll() .requestMatchers("/api/oauth2/**").permitAll() .requestMatchers("/review-likes/**").permitAll() .requestMatchers("/store/**").permitAll() From 7adc9b5cf232b3d19954d85c6a758729f6a28b76 Mon Sep 17 00:00:00 2001 From: HRiver Date: Mon, 27 May 2024 17:51:46 +0900 Subject: [PATCH 31/49] fix: fix issue dto --- .../java/com/ssg/starroad/coupon/DTO/IssueCouponRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/coupon/DTO/IssueCouponRequest.java b/src/main/java/com/ssg/starroad/coupon/DTO/IssueCouponRequest.java index aab6036..f26f3df 100644 --- a/src/main/java/com/ssg/starroad/coupon/DTO/IssueCouponRequest.java +++ b/src/main/java/com/ssg/starroad/coupon/DTO/IssueCouponRequest.java @@ -3,7 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class IssueCouponRequest { - @JsonProperty("userEmail") + @JsonProperty("email") private String email; @JsonProperty("couponId") private Long couponId; From d1c024ae40cc8cd4a2eff214f719a51380e52e9f Mon Sep 17 00:00:00 2001 From: HRiver Date: Tue, 28 May 2024 18:32:39 +0900 Subject: [PATCH 32/49] event : add event --- .../starroad/board/DTO/BoardContentDTO.java | 18 +++++++++ .../com/ssg/starroad/board/DTO/BoardDTO.java | 21 ++++++++++ .../board/controller/BoardController.java | 16 ++++++++ .../controller/BoardImageController.java | 16 ++++++++ .../repository/BoardImageRepository.java | 2 + .../board/repository/BoardRepository.java | 5 +++ .../board/service/BoardImageService.java | 4 ++ .../starroad/board/service/BoardService.java | 6 +++ .../service/impl/BoardImageServiceImpl.java | 9 +++++ .../board/service/impl/BoardServiceImpl.java | 15 +++++++ .../impl/RewardProcessServiceImpl.java | 23 +++++++---- .../ssg/starroad/board/boardserviceTest.java | 31 ++++++++++++++ .../reward/service/RewardServiceTest.java | 40 +++++++++---------- 13 files changed, 178 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/ssg/starroad/board/DTO/BoardContentDTO.java create mode 100644 src/main/java/com/ssg/starroad/board/DTO/BoardDTO.java create mode 100644 src/test/java/com/ssg/starroad/board/boardserviceTest.java diff --git a/src/main/java/com/ssg/starroad/board/DTO/BoardContentDTO.java b/src/main/java/com/ssg/starroad/board/DTO/BoardContentDTO.java new file mode 100644 index 0000000..7cb22d4 --- /dev/null +++ b/src/main/java/com/ssg/starroad/board/DTO/BoardContentDTO.java @@ -0,0 +1,18 @@ +package com.ssg.starroad.board.DTO; + +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class BoardContentDTO { + private Long id; + private LocalDateTime createdAt; + private LocalDateTime modifiedAt; + private String imagePath; + private Long boardId; +} diff --git a/src/main/java/com/ssg/starroad/board/DTO/BoardDTO.java b/src/main/java/com/ssg/starroad/board/DTO/BoardDTO.java new file mode 100644 index 0000000..7701010 --- /dev/null +++ b/src/main/java/com/ssg/starroad/board/DTO/BoardDTO.java @@ -0,0 +1,21 @@ +package com.ssg.starroad.board.DTO; + +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class BoardDTO { + private Long id; + private LocalDateTime createdAt; + private LocalDateTime modifiedAt; + private Long managerId; + private String category; + private String content; + private String title; + private Long userId; +} diff --git a/src/main/java/com/ssg/starroad/board/controller/BoardController.java b/src/main/java/com/ssg/starroad/board/controller/BoardController.java index 38905e4..33ca56a 100644 --- a/src/main/java/com/ssg/starroad/board/controller/BoardController.java +++ b/src/main/java/com/ssg/starroad/board/controller/BoardController.java @@ -1,13 +1,29 @@ package com.ssg.starroad.board.controller; +import com.ssg.starroad.board.DTO.BoardDTO; import com.ssg.starroad.board.service.BoardService; +import com.ssg.starroad.coupon.DTO.CouponDTO; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RequiredArgsConstructor @RestController @RequestMapping("/board") public class BoardController { private final BoardService boardService; + + + @GetMapping("/list") + public ResponseEntity> getBoardList() { + List boardDTOS = boardService.getEventBoards(); + if (boardDTOS.isEmpty()) { + return ResponseEntity.noContent().build(); // 내용이 없을 경우 No Content (204) 반환 + } + return ResponseEntity.ok(boardDTOS); // 내용이 있을 경우 OK (200)와 함께 데이터 반환 + } } diff --git a/src/main/java/com/ssg/starroad/board/controller/BoardImageController.java b/src/main/java/com/ssg/starroad/board/controller/BoardImageController.java index 7ef3be2..76957fc 100644 --- a/src/main/java/com/ssg/starroad/board/controller/BoardImageController.java +++ b/src/main/java/com/ssg/starroad/board/controller/BoardImageController.java @@ -1,13 +1,29 @@ package com.ssg.starroad.board.controller; +import com.ssg.starroad.board.DTO.BoardContentDTO; +import com.ssg.starroad.board.DTO.BoardDTO; import com.ssg.starroad.board.service.BoardImageService; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @RequiredArgsConstructor @RestController @RequestMapping("/board-image") public class BoardImageController { private final BoardImageService boardImageService; + + @GetMapping("/content/{boardId}") + public ResponseEntity getBoardContent(@PathVariable Long boardId) { + BoardContentDTO boardContent = boardImageService.getEventContent(boardId); + if (boardContent==null) { + return ResponseEntity.noContent().build(); // 내용이 없을 경우 No Content (204) 반환 + } + return ResponseEntity.ok(boardContent); // 내용이 있을 경우 OK (200)와 함께 데이터 반환 + } } diff --git a/src/main/java/com/ssg/starroad/board/repository/BoardImageRepository.java b/src/main/java/com/ssg/starroad/board/repository/BoardImageRepository.java index b991da5..23965f6 100644 --- a/src/main/java/com/ssg/starroad/board/repository/BoardImageRepository.java +++ b/src/main/java/com/ssg/starroad/board/repository/BoardImageRepository.java @@ -4,4 +4,6 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface BoardImageRepository extends JpaRepository { + + public BoardImage findByBoardId(Long boardId); } diff --git a/src/main/java/com/ssg/starroad/board/repository/BoardRepository.java b/src/main/java/com/ssg/starroad/board/repository/BoardRepository.java index d7120fd..e4cefdb 100644 --- a/src/main/java/com/ssg/starroad/board/repository/BoardRepository.java +++ b/src/main/java/com/ssg/starroad/board/repository/BoardRepository.java @@ -1,7 +1,12 @@ package com.ssg.starroad.board.repository; import com.ssg.starroad.board.entity.Board; +import com.ssg.starroad.board.enums.BoardCategory; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface BoardRepository extends JpaRepository { + + public List findByCategory(BoardCategory category); } diff --git a/src/main/java/com/ssg/starroad/board/service/BoardImageService.java b/src/main/java/com/ssg/starroad/board/service/BoardImageService.java index 1afab99..0a1d371 100644 --- a/src/main/java/com/ssg/starroad/board/service/BoardImageService.java +++ b/src/main/java/com/ssg/starroad/board/service/BoardImageService.java @@ -1,4 +1,8 @@ package com.ssg.starroad.board.service; +import com.ssg.starroad.board.DTO.BoardContentDTO; + public interface BoardImageService { + + public BoardContentDTO getEventContent (Long boardid); } diff --git a/src/main/java/com/ssg/starroad/board/service/BoardService.java b/src/main/java/com/ssg/starroad/board/service/BoardService.java index 54e5bc2..ba3da29 100644 --- a/src/main/java/com/ssg/starroad/board/service/BoardService.java +++ b/src/main/java/com/ssg/starroad/board/service/BoardService.java @@ -1,4 +1,10 @@ package com.ssg.starroad.board.service; +import com.ssg.starroad.board.DTO.BoardDTO; + +import java.util.List; + public interface BoardService { + + public List getEventBoards(); } diff --git a/src/main/java/com/ssg/starroad/board/service/impl/BoardImageServiceImpl.java b/src/main/java/com/ssg/starroad/board/service/impl/BoardImageServiceImpl.java index eb74c59..2fb06ea 100644 --- a/src/main/java/com/ssg/starroad/board/service/impl/BoardImageServiceImpl.java +++ b/src/main/java/com/ssg/starroad/board/service/impl/BoardImageServiceImpl.java @@ -1,10 +1,19 @@ package com.ssg.starroad.board.service.impl; +import com.ssg.starroad.board.DTO.BoardContentDTO; +import com.ssg.starroad.board.repository.BoardImageRepository; import com.ssg.starroad.board.service.BoardImageService; import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class BoardImageServiceImpl implements BoardImageService { + private final BoardImageRepository boardImageRepository; + private final ModelMapper modelMapper; + @Override + public BoardContentDTO getEventContent(Long boardid) { + return modelMapper.map(boardImageRepository.findByBoardId(boardid), BoardContentDTO.class); + } } diff --git a/src/main/java/com/ssg/starroad/board/service/impl/BoardServiceImpl.java b/src/main/java/com/ssg/starroad/board/service/impl/BoardServiceImpl.java index 0ff5d4c..3d4726b 100644 --- a/src/main/java/com/ssg/starroad/board/service/impl/BoardServiceImpl.java +++ b/src/main/java/com/ssg/starroad/board/service/impl/BoardServiceImpl.java @@ -1,10 +1,25 @@ package com.ssg.starroad.board.service.impl; +import com.ssg.starroad.board.DTO.BoardDTO; +import com.ssg.starroad.board.enums.BoardCategory; +import com.ssg.starroad.board.repository.BoardRepository; import com.ssg.starroad.board.service.BoardService; import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; +import java.util.List; + @Service @RequiredArgsConstructor public class BoardServiceImpl implements BoardService { + + private final BoardRepository boardRepository; + private final ModelMapper modelMapper; + + @Override + public List getEventBoards() { + return boardRepository.findByCategory(BoardCategory.EVENT).stream().map(board -> + modelMapper.map(board,BoardDTO.class)).toList(); + } } diff --git a/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java b/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java index b0dabda..7edfd97 100644 --- a/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java +++ b/src/main/java/com/ssg/starroad/reward/service/impl/RewardProcessServiceImpl.java @@ -29,12 +29,8 @@ public class RewardProcessServiceImpl implements RewardProcessService { public void startRewardProcess(String email) { Long userId = userRepository.findByEmail(email).orElseThrow().getId(); RewardProcess rewardProcess = rewardProcessRepository.findById(userId) - .orElse(new RewardProcess(userId)); // 존재하지 않는 경우 새 객체 생성 - - rewardProcess.setRewardStatus(false); // 기존 로직대로 상태 설정 - rewardProcess.setExpiredAt(LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY))); - rewardProcess.setReviewCount(0); - rewardProcess.setCouponCount(0); + .orElseThrow(); // 존재하지 않는 경우 새 객체 생성 + rewardProcess.setCouponCount(rewardProcess.getCouponCount()+1); rewardProcessRepository.save(rewardProcess); // 저장 } @@ -81,8 +77,19 @@ public void resetStatus(String email) { @Transactional public RewardProcessDTO getProcess(String email){ Long userId = userRepository.findByEmail(email).orElseThrow().getId(); - RewardProcess rewardProcess = rewardProcessRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("Invalid user ID: " + userId)); + RewardProcess rewardProcess = rewardProcessRepository.findById(userId).orElseGet(() -> { + // RewardProcess가 존재하지 않을 경우 새로운 보상 프로세스를 생성합니다. + RewardProcess newRewardProcess = new RewardProcess(); + newRewardProcess.setUserId(userId); + newRewardProcess.setCouponCount(0); + newRewardProcess.setReviewCount(0); + newRewardProcess.setIssueStatus(false); + newRewardProcess.setUsageStatus(false); + newRewardProcess.setRewardStatus(false); + newRewardProcess.setExpiredAt(LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.MONDAY))); // 예시로 만료일을 7일 후로 설정 + return rewardProcessRepository.save(newRewardProcess); + }); + if(Objects.equals(rewardProcess.getExpiredAt(), LocalDate.now())) { rewardProcessRepository.delete(rewardProcess); diff --git a/src/test/java/com/ssg/starroad/board/boardserviceTest.java b/src/test/java/com/ssg/starroad/board/boardserviceTest.java new file mode 100644 index 0000000..904d464 --- /dev/null +++ b/src/test/java/com/ssg/starroad/board/boardserviceTest.java @@ -0,0 +1,31 @@ +package com.ssg.starroad.board; + +import com.ssg.starroad.board.service.BoardImageService; +import com.ssg.starroad.board.service.BoardService; +import com.ssg.starroad.coupon.repository.CouponRepositoryTest; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class boardserviceTest { + private static final Logger log = LogManager.getLogger(CouponRepositoryTest.class); + + + @Autowired + private BoardService boardService; + + @Autowired + private BoardImageService boardImageService; + + @Test + public void testBoardlist(){ + log.info(boardService.getEventBoards()); + } + @Test + public void testBoardImage(){ + log.info(boardImageService.getEventContent(1L)); + } +} diff --git a/src/test/java/com/ssg/starroad/reward/service/RewardServiceTest.java b/src/test/java/com/ssg/starroad/reward/service/RewardServiceTest.java index 86ace5b..f0ad81f 100644 --- a/src/test/java/com/ssg/starroad/reward/service/RewardServiceTest.java +++ b/src/test/java/com/ssg/starroad/reward/service/RewardServiceTest.java @@ -26,24 +26,24 @@ public class RewardServiceTest { @Autowired private ReviewImageService reviewImageService; - @Test - public void test() { - System.out.println("@@@@@@@"); - log.info(reviewImageService.getReviewImages(1L).toString()); - } - @Test - public void testRewardList(){ - log.info(rewardService.getRewardList()); - } - - @Test - public void testObtain(){ - RewardMemberDTO rewardMemberDTO = new RewardMemberDTO(4L,3L); - rewardHistoryService.addReward(rewardMemberDTO); - } - - @Test - public void testMyRewardList(){ - log.info(rewardHistoryService.getRewardHistory(4L).toString()); - } +// @Test +// public void test() { +// System.out.println("@@@@@@@"); +// log.info(reviewImageService.getReviewImages(1L).toString()); +// } +// @Test +// public void testRewardList(){ +// log.info(rewardService.getRewardList()); +// } +// +// @Test +// public void testObtain(){ +// RewardMemberDTO rewardMemberDTO = new RewardMemberDTO(4L,3L); +// rewardHistoryService.addReward(rewardMemberDTO); +// } +// +// @Test +// public void testMyRewardList(){ +// log.info(rewardHistoryService.getRewardHistory(4L).toString()); +// } } From 4c6cfcade030f56041444803f5f4e3079e60bfa2 Mon Sep 17 00:00:00 2001 From: HRiver Date: Tue, 28 May 2024 20:00:07 +0900 Subject: [PATCH 33/49] hotfix : reward image --- .../com/ssg/starroad/reward/DTO/RewardHistoryDTO.java | 2 ++ .../java/com/ssg/starroad/reward/entity/Reward.java | 2 ++ .../repository/impl/RewardHistoryRepositoryCustom.java | 2 +- .../reward/service/impl/RewardHistoryServiceImpl.java | 1 + .../reward/repository/RewardRepositoryTest.java | 10 +++++----- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/ssg/starroad/reward/DTO/RewardHistoryDTO.java b/src/main/java/com/ssg/starroad/reward/DTO/RewardHistoryDTO.java index af241ac..d630d9e 100644 --- a/src/main/java/com/ssg/starroad/reward/DTO/RewardHistoryDTO.java +++ b/src/main/java/com/ssg/starroad/reward/DTO/RewardHistoryDTO.java @@ -22,4 +22,6 @@ public class RewardHistoryDTO { @JsonProperty("reward_count") private Long count; + @JsonProperty("reward_image_path") + private String rewardImagePath; } \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/reward/entity/Reward.java b/src/main/java/com/ssg/starroad/reward/entity/Reward.java index e7dfbed..0026bc3 100644 --- a/src/main/java/com/ssg/starroad/reward/entity/Reward.java +++ b/src/main/java/com/ssg/starroad/reward/entity/Reward.java @@ -23,4 +23,6 @@ public class Reward extends BaseTimeEntity { private Long id; private String name; + + private String rewardImagePath; } diff --git a/src/main/java/com/ssg/starroad/reward/repository/impl/RewardHistoryRepositoryCustom.java b/src/main/java/com/ssg/starroad/reward/repository/impl/RewardHistoryRepositoryCustom.java index 8c92147..f36277c 100644 --- a/src/main/java/com/ssg/starroad/reward/repository/impl/RewardHistoryRepositoryCustom.java +++ b/src/main/java/com/ssg/starroad/reward/repository/impl/RewardHistoryRepositoryCustom.java @@ -21,7 +21,7 @@ public List countRewardHistoryByUserId(Long userId) { QReward reward = QReward.reward; return queryFactory - .select(rewardHistory.rewardId, reward.name, rewardHistory.id.count()) + .select(rewardHistory.rewardId, reward.name ,rewardHistory.id.count(),reward.rewardImagePath) .from(rewardHistory) .leftJoin(reward).on(rewardHistory.rewardId.eq(reward.id)) // 명확한 조인 조건 사용 .where(rewardHistory.user.id.eq(userId)) diff --git a/src/main/java/com/ssg/starroad/reward/service/impl/RewardHistoryServiceImpl.java b/src/main/java/com/ssg/starroad/reward/service/impl/RewardHistoryServiceImpl.java index d94c7ad..b46d6af 100644 --- a/src/main/java/com/ssg/starroad/reward/service/impl/RewardHistoryServiceImpl.java +++ b/src/main/java/com/ssg/starroad/reward/service/impl/RewardHistoryServiceImpl.java @@ -44,6 +44,7 @@ public List getRewardHistory(String email) { .rewardId(record.get(0, Long.class)) .rewardName(record.get(1, String.class)) .count(Objects.requireNonNull(record.get(2, Long.class))) + .rewardImagePath(record.get(3,String.class)) .build(); result.add(dto); } diff --git a/src/test/java/com/ssg/starroad/reward/repository/RewardRepositoryTest.java b/src/test/java/com/ssg/starroad/reward/repository/RewardRepositoryTest.java index d7f4a01..1ad0096 100644 --- a/src/test/java/com/ssg/starroad/reward/repository/RewardRepositoryTest.java +++ b/src/test/java/com/ssg/starroad/reward/repository/RewardRepositoryTest.java @@ -17,9 +17,9 @@ public class RewardRepositoryTest { @Autowired private RewardRepository rewardRepository; - @Test - public void testSave() { - Reward reward = new Reward(null,"세번째별"); - rewardRepository.save(reward); - } +// @Test +// public void testSave() { +// Reward reward = new Reward(null,"세번째별"); +// rewardRepository.save(reward); +// } } From 60a5ae048cc8e8e2df7ff9e2e8442aad09a3d284 Mon Sep 17 00:00:00 2001 From: mariasoo <9723maria@gmail.com> Date: Wed, 29 May 2024 09:09:26 +0900 Subject: [PATCH 34/49] feature:loginupdate --- .../starroad/config/oauth/OAuth2SuccessHandler.java | 3 +++ .../ssg/starroad/user/controller/UserController.java | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java b/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java index eb60e83..877c83e 100644 --- a/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java +++ b/src/main/java/com/ssg/starroad/config/oauth/OAuth2SuccessHandler.java @@ -69,6 +69,9 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo addCookie(response, REFRESH_TOKEN_COOKIE_NAME, refreshToken, REFRESH_TOKEN_EXPIRY); addCookie(response, ACCESS_TOKEN_COOKIE_NAME, accessToken, ACCESS_TOKEN_EXPIRY); + // 세션 무효화 + request.getSession().invalidate(); + // 최종 목적지 URL로 리다이렉트 String targetUrl = determineTargetUrl(request, response, authentication); clearAuthenticationAttributes(request); diff --git a/src/main/java/com/ssg/starroad/user/controller/UserController.java b/src/main/java/com/ssg/starroad/user/controller/UserController.java index c2a0ab0..3d88f7a 100644 --- a/src/main/java/com/ssg/starroad/user/controller/UserController.java +++ b/src/main/java/com/ssg/starroad/user/controller/UserController.java @@ -1,6 +1,7 @@ package com.ssg.starroad.user.controller; +import com.ssg.starroad.common.util.CookieUtil; import com.ssg.starroad.config.jwt.TokenProvider; import com.ssg.starroad.user.dto.*; import com.ssg.starroad.user.entity.User; @@ -34,6 +35,7 @@ public class UserController { private final RefreshTokenService refreshTokenService; private final RefreshTokenRepository refreshTokenRepository; private final S3Uploader s3Uploader; + @PostMapping("/login") public ResponseEntity login(@RequestBody LoginRequest request) { boolean isValidUser = userService.validateUser(request.getEmail(), request.getPassword()); @@ -54,8 +56,9 @@ public ResponseEntity login(@RequestBody LoginRequest request) { LoginResponse loginResponse = new LoginResponse(accessToken, refreshToken,"환영합니다!"); return ResponseEntity.ok().headers(headers).body(loginResponse); + } else { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new LoginResponse("이메일 또는 비밀번호를 잘못 입력했습니다")); } - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new LoginResponse("Invalid credentials")); } @PostMapping("/join/email-check") @@ -128,7 +131,7 @@ public ResponseEntity updateProfile(@RequestBody UserDTO userDTO) { @Transactional @PostMapping("/logout") - public ResponseEntity logout(@RequestBody LogoutRequest logoutRequest, HttpServletRequest request) { + public ResponseEntity logout(@RequestBody LogoutRequest logoutRequest, HttpServletRequest request, HttpServletResponse response) { String email = logoutRequest.getEmail(); String accessToken = logoutRequest.getAccessToken(); @@ -148,6 +151,10 @@ public ResponseEntity logout(@RequestBody LogoutRequest logoutRequest, HttpSe // 세션 무효화 request.getSession().invalidate(); + // JSESSIONID 쿠키 삭제 + CookieUtil.deleteCookie(request, response, "JSESSIONID"); + + return ResponseEntity.ok().body("Logout successful"); } From 01463dca0b1cfc5908afd802f2bc42e54d7ff25a Mon Sep 17 00:00:00 2001 From: ekmbjh Date: Wed, 29 May 2024 09:19:16 +0900 Subject: [PATCH 35/49] =?UTF-8?q?temp=20:=20=EB=A6=AC=EB=B7=B0=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=88=98=EC=A0=95=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/controller/ReviewController.java | 46 ++++-- .../review/service/ReviewService.java | 8 +- .../impl/ReviewFeedbackServiceImpl.java | 12 ++ .../service/impl/ReviewLikeServiceImpl.java | 1 + .../service/impl/ReviewServiceImpl.java | 24 +++- .../ssg/starroad/user/dto/RankUserDTO.java | 33 +++++ .../com/ssg/starroad/user/entity/Follow.java | 4 + .../user/repository/FollowRepository.java | 3 + .../user/repository/UserRepository.java | 19 +++ .../starroad/user/service/UserService.java | 15 +- .../user/service/impl/UserServiceImpl.java | 131 ++++++++++++++++-- 11 files changed, 264 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/ssg/starroad/user/dto/RankUserDTO.java diff --git a/src/main/java/com/ssg/starroad/review/controller/ReviewController.java b/src/main/java/com/ssg/starroad/review/controller/ReviewController.java index cc04c25..5bdc573 100644 --- a/src/main/java/com/ssg/starroad/review/controller/ReviewController.java +++ b/src/main/java/com/ssg/starroad/review/controller/ReviewController.java @@ -1,6 +1,5 @@ package com.ssg.starroad.review.controller; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.ssg.starroad.common.service.S3Uploader; import com.ssg.starroad.review.DTO.*; @@ -12,16 +11,19 @@ import com.ssg.starroad.review.service.ReviewService; import com.ssg.starroad.shop.entity.Store; import com.ssg.starroad.shop.repository.StoreRepository; +import com.ssg.starroad.user.dto.RankUserDTO; import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.repository.UserRepository; +import com.ssg.starroad.user.service.UserService; import lombok.RequiredArgsConstructor; -import org.springframework.http.*; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; +import java.util.ArrayList; +import java.util.List; @RequestMapping("/reviews") @RestController @@ -36,6 +38,7 @@ public class ReviewController { private final S3Uploader s3Uploader; private final ReviewImageRepository imageRepository; private final ReviewImageRepository reviewImageRepository; + private final UserService userService; @PostMapping("/write") @@ -67,12 +70,12 @@ public ResponseEntity getReviewById(@PathVariable Long id) { } - @GetMapping - public ResponseEntity getAllReviews(@RequestParam(defaultValue = "0") int page, + public ResponseEntity getAllReviews(@RequestParam String userEmail, + @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { try { - ResponseReviewDTO responseReviewDTO = reviewService.findAllReview(page, size); + ResponseReviewDTO responseReviewDTO = reviewService.findAllReview(userEmail, page, size); return ResponseEntity.ok(responseReviewDTO); } catch (RuntimeException e) { return ResponseEntity.badRequest().build(); @@ -80,13 +83,13 @@ public ResponseEntity getAllReviews(@RequestParam(defaultValu } @GetMapping("/following") - public ResponseEntity getFollowingReviews(@RequestParam Long id, + public ResponseEntity getFollowingReviews(@RequestParam String userEmail, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { - System.out.printf("following 리뷰 메소드 진입"); + System.out.printf("CReviewController::getFollowingReviews userEmail : %s\n", userEmail); try { System.out.printf("try문 실행"); - ResponseReviewDTO responseReviewDTO = reviewService.findFollowingReview(id, page, size); + ResponseReviewDTO responseReviewDTO = reviewService.findFollowingReview(userEmail, page, size); return ResponseEntity.ok(responseReviewDTO); } catch (RuntimeException e) { System.out.printf(e.getMessage()); @@ -148,5 +151,28 @@ public ResponseEntity submitSurvey( return ResponseEntity.ok("설문이 성공적으로 제출되었습니다!"); } + @GetMapping("/rank") + public ResponseEntity> getRankUser(@RequestParam String userEmail) { + List rankUserDTOS = userService.getRankUser(userEmail); + + return ResponseEntity.ok(rankUserDTOS); + } + + @GetMapping("/allUser") + public ResponseEntity> getAllUser(@RequestParam String userEmail) { + List rankUserDTOS = userService.getAllUser(userEmail); + + return ResponseEntity.ok(rankUserDTOS); + } + + @PostMapping("/addFollowUser") + public ResponseEntity getAllReviews(@RequestParam String userName, + @RequestParam String userEmail + ) { + System.out.printf("CReviewController getAllReviews userName : %s, userEmail : %s", userName, userEmail); + String followState = userService.addFollowUser(userName, userEmail); + + return ResponseEntity.ok("팔로우 성공"); + } } diff --git a/src/main/java/com/ssg/starroad/review/service/ReviewService.java b/src/main/java/com/ssg/starroad/review/service/ReviewService.java index 5d1119a..2d93371 100644 --- a/src/main/java/com/ssg/starroad/review/service/ReviewService.java +++ b/src/main/java/com/ssg/starroad/review/service/ReviewService.java @@ -1,9 +1,7 @@ package com.ssg.starroad.review.service; import com.ssg.starroad.review.DTO.ResponseReviewDTO; -import com.ssg.starroad.review.DTO.ReviewDTO; import com.ssg.starroad.review.entity.Review; -import com.ssg.starroad.shop.DTO.StoreWithReviewDTO; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; @@ -13,7 +11,7 @@ public interface ReviewService { Long countReviewsByUserId(Long userId); - + Review createReview(Review review); Optional getReviewById(Long id); @@ -26,7 +24,7 @@ public interface ReviewService { ResponseEntity callOcrApi(MultipartFile imageFile) throws IOException; - ResponseReviewDTO findAllReview(int pageNo, int pageSize); + ResponseReviewDTO findAllReview(String userEmail, int pageNo, int pageSize); - ResponseReviewDTO findFollowingReview(Long id, int pageNo, int pageSize); + ResponseReviewDTO findFollowingReview(String userEmail, int pageNo, int pageSize); } diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewFeedbackServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewFeedbackServiceImpl.java index b7fc865..5f5851f 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewFeedbackServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewFeedbackServiceImpl.java @@ -8,6 +8,7 @@ import com.ssg.starroad.review.repository.ReviewRepository; import com.ssg.starroad.review.service.ReviewFeedbackService; import com.ssg.starroad.review.service.ReviewSelectionService; +import com.ssg.starroad.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +23,7 @@ public class ReviewFeedbackServiceImpl implements ReviewFeedbackService { private final ReviewRepository reviewRepository; private final S3Uploader s3Uploader; private final ReviewSelectionService reviewSelectionService; + private final UserRepository userRepository; @Override public List getReviewFeedback(Long reviewId) { @@ -37,6 +39,16 @@ public List getReviewFeedback(Long reviewId) { @Transactional @Override public ReviewFeedbackDTO addReviewFeedback(ReviewFeedbackDTO reviewFeedbackDTO) { + Long userId = reviewFeedbackDTO.getId(); + userRepository.findById(userId).ifPresent(user -> { + Integer reviewExp = user.getReviewExp(); + Integer point = user.getPoint(); + Integer additionalPoint = reviewExp < 100 ? 100 : reviewExp < 200 ? 200 : 300; + + userRepository.updatePointById(userId, point + additionalPoint); + userRepository.updateReviewExpById(userId, reviewExp + 10); + }); + // reviewFeedbackSelection을 ','를 기준으로 나눠 String 배열에 담는다. String[] selections = reviewFeedbackDTO.getReviewFeedbackSelection().split(","); System.out.printf("selections : " + selections); diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java index 7feb7d6..f02a430 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewLikeServiceImpl.java @@ -31,6 +31,7 @@ public List getLikedReviewIdsByUserId(Long userId) { @Override @Transactional public ReviewLikeDTO addLike(String userEmail, Long reviewId) { + System.out.println("Function(addLike) - userEmail: " + userEmail + ", reviewId: " + reviewId); User user = userRepository.findByEmail(userEmail) .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다.")); Long userId = user.getId(); diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java index c1b6509..0983e45 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java @@ -6,12 +6,13 @@ import com.ssg.starroad.review.DTO.ReviewImageDTO; import com.ssg.starroad.review.entity.Review; import com.ssg.starroad.review.repository.ReviewFollowRepository; +import com.ssg.starroad.review.repository.ReviewLikeRepository; import com.ssg.starroad.review.repository.ReviewRepository; import com.ssg.starroad.review.service.ReviewFeedbackService; import com.ssg.starroad.review.service.ReviewImageService; import com.ssg.starroad.review.service.ReviewService; +import com.ssg.starroad.user.repository.UserRepository; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -40,9 +41,12 @@ public class ReviewServiceImpl implements ReviewService { private final ReviewFeedbackService reviewFeedbackService; private final ReviewFollowRepository reviewFollowRepository; + private final ReviewLikeRepository reviewLikeRepository; + private final UserRepository userRepository; + @Override public Long countReviewsByUserId(Long userId) { - return reviewRepository.countByUserId(userId); + return reviewRepository.countByUserId(userId); } @Override @@ -128,12 +132,12 @@ public ResponseEntity callOcrApi(MultipartFile imageFile) throws IOExcep } @Override - public ResponseReviewDTO findAllReview(int pageNo, int pageSize) { + public ResponseReviewDTO findAllReview(String userEmail, int pageNo, int pageSize) { // 최신순으로 정렬된 Pageable 객체 생성 Pageable pageable = PageRequest.of(pageNo, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")); // 정렬된 Pageable 객체를 사용하여 리뷰 페이지 가져오기 Page reviewPage = reviewRepository.findAll(pageable); - + Long userId = userRepository.findByEmail(userEmail).orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")).getId(); // Long totalReviewCount = reviewRepository.countByStoreId(id); List reviewDTOList = reviewPage.stream() @@ -141,6 +145,7 @@ public ResponseReviewDTO findAllReview(int pageNo, int pageSize) { List reviewImageDTOs = reviewImageService.getReviewImages(review.getId()); List reviewFeedbackDTOs = reviewFeedbackService.getReviewFeedback(review.getId()); Long userReviewCount = countReviewsByUserId(review.getUser().getId()); + boolean isLiked = reviewLikeRepository.existsByUser_IdAndReview_Id(userId, review.getId()); return ReviewDTO.builder() .id(review.getId()) @@ -169,15 +174,19 @@ public ResponseReviewDTO findAllReview(int pageNo, int pageSize) { } @Override - public ResponseReviewDTO findFollowingReview(Long id, int pageNo, int pageSize) { + public ResponseReviewDTO findFollowingReview(String userEmail, int pageNo, int pageSize) { // 최신순으로 정렬된 Pageable 객체 생성 Pageable pageable = PageRequest.of(pageNo, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")); // 정렬된 Pageable 객체를 사용하여 리뷰 페이지 가져오기 - List toUserIds = reviewFollowRepository.findToUserIdsByFromUserId(id); + // 사용자 이메일을 통해 사용자 ID를 조회합니다. + Long userId = userRepository.findByEmail(userEmail).orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")).getId(); + + List toUserIds = reviewFollowRepository.findToUserIdsByFromUserId(userId); Page reviewPage = reviewRepository.findAllByUserIds(toUserIds, pageable); + // Page reviewPage = reviewFollowRepository.findToUserIdsByFromUserId(id).stream().map(followId -> reviewRepository.findAllWithPageByUserId(followId, pageable)).toList().stream().findFirst().orElse(null); List reviewDTOList = reviewPage.stream() @@ -185,7 +194,7 @@ public ResponseReviewDTO findFollowingReview(Long id, int pageNo, int pageSize) List reviewImageDTOs = reviewImageService.getReviewImages(review.getId()); List reviewFeedbackDTOs = reviewFeedbackService.getReviewFeedback(review.getId()); Long userReviewCount = countReviewsByUserId(review.getUser().getId()); - + boolean isLiked = reviewLikeRepository.existsByUser_IdAndReview_Id(userId, review.getId()); return ReviewDTO.builder() .id(review.getId()) .userId(review.getUser().getId()) @@ -201,6 +210,7 @@ public ResponseReviewDTO findFollowingReview(Long id, int pageNo, int pageSize) .reviewcount(userReviewCount) .reviewImages(reviewImageDTOs) .reviewFeedbacks(reviewFeedbackDTOs) + .isLiked(isLiked) .build(); }) .collect(Collectors.toList()); diff --git a/src/main/java/com/ssg/starroad/user/dto/RankUserDTO.java b/src/main/java/com/ssg/starroad/user/dto/RankUserDTO.java new file mode 100644 index 0000000..52a5bc7 --- /dev/null +++ b/src/main/java/com/ssg/starroad/user/dto/RankUserDTO.java @@ -0,0 +1,33 @@ +package com.ssg.starroad.user.dto; + +import com.ssg.starroad.user.entity.User; +import com.ssg.starroad.user.enums.ActiveStatus; +import lombok.*; + +@Getter +@Setter +@ToString +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class RankUserDTO { + private Long id; + private String name; + private String nickname; + private String email; + private String imagePath; + private int reviewExp; + private int point; + private ActiveStatus activeStatus; + + public RankUserDTO(User user) { + this.id = user.getId(); + this.name = user.getName(); + this.nickname = user.getNickname(); + this.email = user.getEmail(); + this.imagePath = user.getImagePath(); + this.reviewExp = user.getReviewExp(); + this.point = user.getPoint(); + this.activeStatus = user.getActiveStatus(); + } +} diff --git a/src/main/java/com/ssg/starroad/user/entity/Follow.java b/src/main/java/com/ssg/starroad/user/entity/Follow.java index 319cda5..633f9ed 100644 --- a/src/main/java/com/ssg/starroad/user/entity/Follow.java +++ b/src/main/java/com/ssg/starroad/user/entity/Follow.java @@ -2,6 +2,8 @@ import com.ssg.starroad.common.entity.BaseTimeEntity; import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -11,6 +13,8 @@ @Getter @Entity @NoArgsConstructor(access = PROTECTED) +@AllArgsConstructor +@Builder public class Follow extends BaseTimeEntity { @Id diff --git a/src/main/java/com/ssg/starroad/user/repository/FollowRepository.java b/src/main/java/com/ssg/starroad/user/repository/FollowRepository.java index a6dde75..3023d0d 100644 --- a/src/main/java/com/ssg/starroad/user/repository/FollowRepository.java +++ b/src/main/java/com/ssg/starroad/user/repository/FollowRepository.java @@ -8,6 +8,7 @@ import org.springframework.data.repository.query.Param; import java.util.List; +import java.util.Optional; public interface FollowRepository extends JpaRepository { @Query("SELECT COUNT(f) FROM Follow f WHERE f.fromUser.id = :userId") @@ -25,4 +26,6 @@ public interface FollowRepository extends JpaRepository { @Modifying @Query("DELETE FROM Follow f WHERE f.fromUser.id = :fromUserId AND f.toUser.id = :toUserId") void deleteByFromUserIdAndToUserId(@Param("fromUserId") Long fromUserId, @Param("toUserId") Long toUserId); + + Optional findByFromUserAndToUser(User fromUser, User toUser); } diff --git a/src/main/java/com/ssg/starroad/user/repository/UserRepository.java b/src/main/java/com/ssg/starroad/user/repository/UserRepository.java index 58be692..8461f61 100644 --- a/src/main/java/com/ssg/starroad/user/repository/UserRepository.java +++ b/src/main/java/com/ssg/starroad/user/repository/UserRepository.java @@ -2,13 +2,32 @@ import com.ssg.starroad.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import java.util.Optional; public interface UserRepository extends JpaRepository { Optional findByEmail(String email); + boolean existsByNickname(String nickname); + Optional findByid(Long id); + Optional findBynickname(String nickName); + @Transactional + @Modifying + @Query("UPDATE User u SET u.reviewExp = :reviewExp WHERE u.id = :id") + int updateReviewExpById(@Param("id") Long id, @Param("reviewExp") int reviewExp); + + // point 필드를 업데이트하는 메소드 + @Transactional + @Modifying + @Query("UPDATE User u SET u.point = :point WHERE u.id = :id") + int updatePointById(@Param("id") Long id, @Param("point") int point); + + } diff --git a/src/main/java/com/ssg/starroad/user/service/UserService.java b/src/main/java/com/ssg/starroad/user/service/UserService.java index 22b8cbd..3036fa0 100644 --- a/src/main/java/com/ssg/starroad/user/service/UserService.java +++ b/src/main/java/com/ssg/starroad/user/service/UserService.java @@ -1,11 +1,13 @@ package com.ssg.starroad.user.service; +import com.ssg.starroad.user.dto.MypageDTO; +import com.ssg.starroad.user.dto.RankUserDTO; import com.ssg.starroad.user.dto.UserDTO; import com.ssg.starroad.user.entity.User; import org.springframework.security.core.userdetails.UserDetailsService; -import com.ssg.starroad.user.dto.MypageDTO; -import org.springframework.web.multipart.MultipartFile; + +import java.util.List; import java.util.Optional; public interface UserService extends UserDetailsService { @@ -31,9 +33,18 @@ public interface UserService extends UserDetailsService { void inactiveUser(String email); Optional findNicknameById(Long id); + public MypageDTO getMypage(String email); + public void saveProfileimg(String email, String path); + public String getProfileimg(String email); + public void deleteProfileimg(String email); + public List getRankUser(String email); + + public List getAllUser(String email); + + public String addFollowUser(String userName, String email); } diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index 73fdf80..2cc55b9 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -1,24 +1,29 @@ package com.ssg.starroad.user.service.impl; +import com.ssg.starroad.user.dto.MypageDTO; +import com.ssg.starroad.user.dto.RankUserDTO; import com.ssg.starroad.user.dto.UserDTO; +import com.ssg.starroad.user.entity.Follow; import com.ssg.starroad.user.entity.User; -import com.ssg.starroad.user.enums.ProviderType; import com.ssg.starroad.user.enums.ActiveStatus; +import com.ssg.starroad.user.enums.ProviderType; +import com.ssg.starroad.user.repository.FollowRepository; import com.ssg.starroad.user.repository.UserRepository; import com.ssg.starroad.user.service.UserService; import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.ssg.starroad.user.dto.MypageDTO; -import org.modelmapper.ModelMapper; -import org.springframework.web.multipart.MultipartFile; - +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import java.util.Comparator; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -28,6 +33,8 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; private final BCryptPasswordEncoder passwordEncoder; private final ModelMapper modelMapper; + private final UrlBasedCorsConfigurationSource corsConfigurationSource; + private final FollowRepository followRepository; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { @@ -73,6 +80,7 @@ public Long save(UserDTO userDTO) { .build(); return userRepository.save(user).getId(); } + public boolean validateUser(String email, String password) { Optional userOpt = userRepository.findByEmail(email); @@ -142,7 +150,7 @@ public MypageDTO getMypage(String email) { @Override public void saveProfileimg(String email, String path) { - User user=userRepository.findByEmail(email).orElseThrow(); + User user = userRepository.findByEmail(email).orElseThrow(); user.setProfileimgPath(path); userRepository.save(user); } @@ -189,14 +197,121 @@ public void inactiveUser(String email) { } public String getProfileimg(String email) { - User user =userRepository.findByEmail(email).orElseThrow(); + User user = userRepository.findByEmail(email).orElseThrow(); return user.getImagePath(); } @Override public void deleteProfileimg(String email) { - User user =userRepository.findByEmail(email).orElseThrow(); + User user = userRepository.findByEmail(email).orElseThrow(); user.setProfileimgPath(""); userRepository.save(user); } + + @Override + public List getRankUser(String email) { + List users = userRepository.findAll(); + + Long id = findByEmail(email).get().getId(); + + // id 값과 동일한 값을 제외하고 나머지 사용자들 선택 + List filteredUsers = users.stream() + .filter(user -> !user.getId().equals(id)) + .collect(Collectors.toList()); + + // reviewExp 필드를 기준으로 상위 3명의 사용자만 선택 + List topUsers = users.stream() + .sorted(Comparator.comparingInt(User::getReviewExp).reversed()) + .limit(3) + .collect(Collectors.toList()); + + // 상위 3명의 사용자 목록을 출력 또는 다른 작업 수행 + topUsers.forEach(System.out::println); + + // User 객체를 RankUserDTO 객체로 변환 + List rankUserDTOS = topUsers.stream() + .map(user -> RankUserDTO.builder() + .id(user.getId()) + .name(user.getName()) + .nickname(user.getNickname()) + .email(user.getEmail()) + .imagePath(user.getImagePath()) + .reviewExp(user.getReviewExp()) + .point(user.getPoint()) + .activeStatus(user.getActiveStatus()) + .build()) + .collect(Collectors.toList()); + + return rankUserDTOS; + } + + @Override + public List getAllUser(String email) { + List users = userRepository.findAll(); + + Long id = findByEmail(email).get().getId(); + + // id 값과 동일한 값을 제외하고 나머지 사용자들 선택 + List filteredUsers = users.stream() + .filter(user -> !user.getId().equals(id)) + .collect(Collectors.toList()); + + // reviewExp 필드를 기준으로 상위 3명의 사용자만 선택 + List topUsers = users.stream() + .sorted(Comparator.comparingInt(User::getReviewExp).reversed()) + .collect(Collectors.toList()); + + // 상위 3명의 사용자 목록을 출력 또는 다른 작업 수행 + topUsers.forEach(System.out::println); + + // User 객체를 RankUserDTO 객체로 변환 + List rankUserDTOS = topUsers.stream() + .map(user -> RankUserDTO.builder() + .id(user.getId()) + .name(user.getName()) + .nickname(user.getNickname()) + .email(user.getEmail()) + .imagePath(user.getImagePath()) + .reviewExp(user.getReviewExp()) + .point(user.getPoint()) + .activeStatus(user.getActiveStatus()) + .build()) + .collect(Collectors.toList()); + + return rankUserDTOS; + } + + + @Transactional + public String addFollowUser(String userName, String email) { + System.out.println("userName, email : " + userName + ", " + email); + Optional fromUserOpt = userRepository.findByEmail(email); + Optional toUserOpt = userRepository.findBynickname(userName); + System.out.println("fromUserOpt, toUserOpt : " + fromUserOpt + ", " + toUserOpt); + if (!fromUserOpt.isPresent() || !toUserOpt.isPresent()) { + return "유효하지 않은 사용자 정보입니다."; + } + + User fromUser = fromUserOpt.get(); + User toUser = toUserOpt.get(); + + System.out.println("fromUser, toUser : " + fromUser + ", " + toUser); + + Optional existingFollow = followRepository.findByFromUserAndToUser(fromUser, toUser); + + // 기존 팔로우 관계가 존재하면 삭제, 그렇지 않으면 새로운 팔로우 관계 추가 + if (existingFollow.isPresent()) { + System.out.println("해제 실행"); + followRepository.delete(existingFollow.get()); + return "팔로우 관계가 해제되었습니다."; + } else { + Follow newFollow = Follow.builder() + .fromUser(fromUser) + .toUser(toUser) + .build(); + System.out.println("저장 실행"); + followRepository.save(newFollow); + return "새로운 팔로우 관계가 생성되었습니다."; + } + } } From 5253e2edc5dfa6cf57b080b3820db36fcd13c633 Mon Sep 17 00:00:00 2001 From: choijh9023 Date: Wed, 29 May 2024 09:55:37 +0900 Subject: [PATCH 36/49] Hotfix : about StoreReview likedSort --- .../shop/controller/StoreController.java | 19 ++++++++----------- .../starroad/shop/service/StoreService.java | 2 +- .../shop/service/impl/StoreServiceImpl.java | 18 +++++++++++++----- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java index ab1a961..4d66c97 100644 --- a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java +++ b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java @@ -35,12 +35,11 @@ public ResponseEntity StoreGetWithReviewById( @RequestParam String userEmail, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, - @RequestParam(required = false) String filter) { + @RequestParam(required = false) String filter, + @RequestParam(required = false) String sort) { try { - - String test = "choijh9023@naver.com"; - log.info("Received request for store ID: {}, userEmail: {}, page: {}, size: {}, filter: ", id, test, page, size,filter); - StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, test, page, size,filter); + log.info("Received request for store ID: {}, userEmail: {}, page: {}, size: {}, filter: {}, sort: {}", id, userEmail, page, size, filter, sort); + StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, userEmail, page, size, filter, sort); return ResponseEntity.ok(storeWithReviewDTO); } catch (RuntimeException e) { log.error("리뷰가 있는 상점을 가져오는데 실패했습니다", e); @@ -48,18 +47,16 @@ public ResponseEntity StoreGetWithReviewById( } } - // 특정 매장의 가이드 맵을 조회하는 요청을 처리하는 메소드입니다. + @GetMapping("/{id}/guidemap") public ResponseEntity StoreGetGuidemapById(@PathVariable Long id) { - try { - // 매장 서비스를 통해 특정 매장의 가이드 맵을 조회합니다. + log.info("Fetching guide map for store ID: {}", id); StoreDTO storeDTO = storeService.findStore(id); - // 조회된 가이드 맵을 응답으로 반환합니다. - log.info(storeDTO.toString()); + log.info("StoreDTO: {}", storeDTO); return ResponseEntity.ok(storeDTO); } catch (RuntimeException e) { - // 조회 중에 예외가 발생한 경우 404 응답을 반환합니다. + log.error("Failed to fetch guide map for store ID: {}", id, e); return ResponseEntity.notFound().build(); } } diff --git a/src/main/java/com/ssg/starroad/shop/service/StoreService.java b/src/main/java/com/ssg/starroad/shop/service/StoreService.java index d8f6395..4b88431 100644 --- a/src/main/java/com/ssg/starroad/shop/service/StoreService.java +++ b/src/main/java/com/ssg/starroad/shop/service/StoreService.java @@ -10,7 +10,7 @@ public interface StoreService { List searchStoreList(Long id); - StoreWithReviewDTO findStoreWithReview(Long storeId,String userEmail, int pageNo,int pageSize,String filter); + StoreWithReviewDTO findStoreWithReview(Long storeId,String userEmail, int pageNo,int pageSize,String filter,String sort); StoreDTO findStore(Long id); String findStoreTypeByName(String name); } diff --git a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java index 7b30869..585e449 100644 --- a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java +++ b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java @@ -52,11 +52,20 @@ public List searchStoreList(Long id) { return storeDTOList; } @Override - public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize,String filter) { + public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter, String sort) { // 주어진 storeId로 스토어를 조회합니다. Store store = storeRepository.findById(storeId).orElseThrow(() -> new RuntimeException("존재하지 않는 스토어입니다.")); + + // 정렬 방식 설정 + Sort sortOption; + if ("likes".equalsIgnoreCase(sort)) { + sortOption = Sort.by(Sort.Direction.DESC, "likeCount"); + } else { + sortOption = Sort.by(Sort.Direction.DESC, "createdAt"); + } + // 페이지 요청 객체를 생성합니다. - Pageable pageable = PageRequest.of(pageNo, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")); + Pageable pageable = PageRequest.of(pageNo, pageSize, sortOption); // 스토어 ID와 필터 조건에 따라 리뷰 페이지를 조회합니다. Page reviewPage; @@ -67,10 +76,11 @@ public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, in // 필터가 있으면 필터 조건에 맞는 리뷰만 조회합니다. reviewPage = reviewRepository.findAllWithPageByStoreIdAndReviewFeedbackSelection(storeId, filter, pageable); } + // 스토어 ID로 전체 리뷰 개수를 조회합니다. Long totalReviewCount = reviewRepository.countByStoreId(storeId); - // 특정 피드백을 받은 리뷰 개수를 조회합니다.(4개// 필수 선택지 ) + // 특정 피드백을 받은 리뷰 개수를 조회합니다. long revisitCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "재방문 하고 싶어요"); long serviceSatisfactionCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "서비스가 마음에 들어요"); long reasonablePriceCount = reviewRepository.countByStoreIdAndReviewFeedbackSelection(storeId, "가격이 합리적입니다"); @@ -78,14 +88,12 @@ public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, in // 사용자 이메일을 통해 사용자 ID를 조회합니다. Long userId = userRepository.findByEmail(userEmail).orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")).getId(); -// List likedReviewIds = reviewLikeRepository.findLikedReviewIdsByUserId(userId); List reviewDTOList = reviewPage.stream() .map(review -> { List reviewImageDTOs = reviewImageService.getReviewImages(review.getId()); List reviewFeedbackDTOs = reviewFeedbackService.getReviewFeedback(review.getId()); Long userReviewCount = reviewService.countReviewsByUserId(review.getUser().getId()); -// boolean isLiked = likedReviewIds.contains(review.getId()); boolean isLiked = reviewLikeRepository.existsByUser_IdAndReview_Id(userId, review.getId()); return ReviewDTO.builder() From b43870209e4b9c07a0741bc97225c1494b0a272d Mon Sep 17 00:00:00 2001 From: HRiver Date: Wed, 29 May 2024 11:08:09 +0900 Subject: [PATCH 37/49] mypage : add myreview --- .../review/controller/ReviewController.java | 13 +++++++ .../review/service/ReviewService.java | 2 + .../service/impl/ReviewServiceImpl.java | 39 +++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/src/main/java/com/ssg/starroad/review/controller/ReviewController.java b/src/main/java/com/ssg/starroad/review/controller/ReviewController.java index f2a3395..d0ed550 100644 --- a/src/main/java/com/ssg/starroad/review/controller/ReviewController.java +++ b/src/main/java/com/ssg/starroad/review/controller/ReviewController.java @@ -93,6 +93,19 @@ public ResponseEntity getFollowingReviews(@RequestParam Long return ResponseEntity.badRequest().build(); } } + @GetMapping("/myreveiw") + public ResponseEntity getMyReviews(@RequestParam String email, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size) { + System.out.printf("my 리뷰 메소드 진입"); + try { + ResponseReviewDTO responseReviewDTO = reviewService.getUserReview(email, page, size); + return ResponseEntity.ok(responseReviewDTO); + } catch (RuntimeException e) { + System.out.printf(e.getMessage()); + return ResponseEntity.badRequest().build(); + } + } @PostMapping("/submit") diff --git a/src/main/java/com/ssg/starroad/review/service/ReviewService.java b/src/main/java/com/ssg/starroad/review/service/ReviewService.java index 5d1119a..ec94770 100644 --- a/src/main/java/com/ssg/starroad/review/service/ReviewService.java +++ b/src/main/java/com/ssg/starroad/review/service/ReviewService.java @@ -29,4 +29,6 @@ public interface ReviewService { ResponseReviewDTO findAllReview(int pageNo, int pageSize); ResponseReviewDTO findFollowingReview(Long id, int pageNo, int pageSize); + + ResponseReviewDTO getUserReview(String email,int pageNo, int pageSize); } diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java index c1b6509..cdb800e 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java @@ -10,6 +10,7 @@ import com.ssg.starroad.review.service.ReviewFeedbackService; import com.ssg.starroad.review.service.ReviewImageService; import com.ssg.starroad.review.service.ReviewService; +import com.ssg.starroad.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -39,6 +40,7 @@ public class ReviewServiceImpl implements ReviewService { private final ReviewImageService reviewImageService; // ReviewImageService 인젝션 private final ReviewFeedbackService reviewFeedbackService; private final ReviewFollowRepository reviewFollowRepository; + private final UserRepository userRepository; @Override public Long countReviewsByUserId(Long userId) { @@ -211,4 +213,41 @@ public ResponseReviewDTO findFollowingReview(Long id, int pageNo, int pageSize) .hasNext(reviewPage.hasNext()) .build(); } + + public ResponseReviewDTO getUserReview(String email,int pageNo, int pageSize){ + Pageable pageable = PageRequest.of(pageNo, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")); + Long userId = userRepository.findByEmail(email).orElseThrow().getId(); + Page reviewPage = reviewRepository.findAllByUserIds(Collections.singletonList(userId), pageable); + + List reviewDTOList = reviewPage.stream() + .map(review -> { + List reviewImageDTOs = reviewImageService.getReviewImages(review.getId()); + List reviewFeedbackDTOs = reviewFeedbackService.getReviewFeedback(review.getId()); + Long userReviewCount = countReviewsByUserId(review.getUser().getId()); + + return ReviewDTO.builder() + .id(review.getId()) + .userId(review.getUser().getId()) + .userNickname(review.getUser().getNickname()) + .imagePath(review.getUser().getImagePath()) + .storeId(review.getStore().getId()) + .visible(review.isVisible()) + .createDate(review.getCreatedAt()) + .likeCount(review.getLikeCount()) + .contents(review.getContents()) + .summary(review.getSummary()) + .confidence(review.getConfidence()) + .reviewcount(userReviewCount) + .reviewImages(reviewImageDTOs) + .reviewFeedbacks(reviewFeedbackDTOs) + .build(); + }) + .collect(Collectors.toList()); + return ResponseReviewDTO.builder() + .reviews(reviewDTOList) + .pageNumber(reviewPage.getNumber()) + .pageSize(reviewPage.getSize()) + .hasNext(reviewPage.hasNext()) + .build(); + } } From 42994326d3c1a99d226ea31b2273bbe4ad2dba8f Mon Sep 17 00:00:00 2001 From: ekmbjh Date: Wed, 29 May 2024 17:51:49 +0900 Subject: [PATCH 38/49] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=ED=8C=94=EB=A1=9C?= =?UTF-8?q?=EC=9A=B0,=20=EC=A2=8B=EC=95=84=EC=9A=94,=20=ED=8F=AC=EC=9D=B8?= =?UTF-8?q?=ED=8A=B8/=EA=B2=BD=ED=97=98=EC=B9=98,=20OCR=20=ED=82=A4=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95,=20Summary=20api,=20id=20->=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20email=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/service/clova/Sentiment.java | 31 +++- .../ssg/starroad/review/DTO/ReviewDTO.java | 4 +- .../review/DTO/ReviewSentimentDTO.java | 4 +- .../review/controller/ReviewController.java | 59 +------ .../starroad/review/entity/ReviewKeyword.java | 2 + .../review/entity/ReviewSentiment.java | 4 +- .../review/service/ReviewService.java | 5 + .../impl/ReviewFeedbackServiceImpl.java | 21 ++- .../impl/ReviewReceiptServiceImpl.java | 12 +- .../impl/ReviewSentimentServiceImpl.java | 11 +- .../service/impl/ReviewServiceImpl.java | 161 +++++++++++++++++- .../starroad/shop/service/StoreService.java | 15 +- .../shop/service/impl/StoreServiceImpl.java | 19 ++- .../com/ssg/starroad/user/entity/User.java | 17 +- .../user/service/impl/UserServiceImpl.java | 2 + 15 files changed, 259 insertions(+), 108 deletions(-) diff --git a/src/main/java/com/ssg/starroad/common/service/clova/Sentiment.java b/src/main/java/com/ssg/starroad/common/service/clova/Sentiment.java index 9f4b4dc..2532905 100644 --- a/src/main/java/com/ssg/starroad/common/service/clova/Sentiment.java +++ b/src/main/java/com/ssg/starroad/common/service/clova/Sentiment.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.ssg.starroad.review.DTO.SentimentDTO; +import com.ssg.starroad.review.DTO.SentimentDetailDTO; import com.ssg.starroad.review.entity.ReviewSentiment; import com.ssg.starroad.review.enums.ConfidenceType; import lombok.RequiredArgsConstructor; @@ -31,7 +33,7 @@ public class Sentiment { private final String clovaUrl = "https://naveropenapi.apigw.ntruss.com/sentiment-analysis/v1/analyze"; - public List getSentiment(String content) { + public SentimentDTO getSentiment(String content) { // 요청 설정 // 요청 헤더 설정 @@ -52,6 +54,8 @@ public List getSentiment(String content) { // 응답 받기 위해 준비 List sentimentList = new ArrayList<>(); + List detailDTOList = new ArrayList<>(); + SentimentDTO build = null; try { // 응답 파싱하기 ObjectMapper objectMapper = new ObjectMapper(); @@ -65,7 +69,19 @@ public List getSentiment(String content) { // ReviewSentiment 객체로 변환 for (JsonNode sentence : sentences) { - ReviewSentiment reviewSentiment = ReviewSentiment.builder() +// ReviewSentiment reviewSentiment = ReviewSentiment.builder() +// .content(sentence.get("content").asText()) +// .confidence(ConfidenceType.valueOf(sentence.get("sentiment").asText().toUpperCase())) +// .offset(sentence.get("offset").asInt()) +// .length(sentence.get("length").asInt()) +// .highlightLength(sentence.get("highlights").get(0).get("length").asInt()) +// .highlightOffset(sentence.get("highlights").get(0).get("offset").asInt()) +// .confidenceStat(confidenceType) +// .build(); + + // ReviewSentiment 객체 리스트에 추가 +// sentimentList.add(reviewSentiment); + SentimentDetailDTO detailDTO = SentimentDetailDTO.builder() .content(sentence.get("content").asText()) .confidence(ConfidenceType.valueOf(sentence.get("sentiment").asText().toUpperCase())) .offset(sentence.get("offset").asInt()) @@ -74,15 +90,20 @@ public List getSentiment(String content) { .highlightOffset(sentence.get("highlights").get(0).get("offset").asInt()) .build(); - // ReviewSentiment 객체 리스트에 추가 - sentimentList.add(reviewSentiment); + detailDTOList.add(detailDTO); } + + build = SentimentDTO.builder() + .documentConfidence(confidenceType) + .sentimentDetailDTOList(detailDTOList) + .build(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (JsonProcessingException e) { e.printStackTrace(); } finally { - return sentimentList; + return build; +// return sentimentList; } } } diff --git a/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java b/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java index b245290..30ab482 100644 --- a/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java +++ b/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java @@ -1,14 +1,11 @@ package com.ssg.starroad.review.DTO; import com.ssg.starroad.review.enums.ConfidenceType; -import com.ssg.starroad.user.entity.User; -import jakarta.persistence.Embedded; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import lombok.*; import org.springframework.web.multipart.MultipartFile; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -22,6 +19,7 @@ public class ReviewDTO { private String userNickname; private Long id; private Long userId; + private String userEmail; private String imagePath; private Long storeId; private boolean visible; diff --git a/src/main/java/com/ssg/starroad/review/DTO/ReviewSentimentDTO.java b/src/main/java/com/ssg/starroad/review/DTO/ReviewSentimentDTO.java index 938ca04..fa9c808 100644 --- a/src/main/java/com/ssg/starroad/review/DTO/ReviewSentimentDTO.java +++ b/src/main/java/com/ssg/starroad/review/DTO/ReviewSentimentDTO.java @@ -10,8 +10,8 @@ public class ReviewSentimentDTO { private Long id; private Long reviewId; // Review 엔티티의 ID private String content; - private int offset; - private int length; + private int totalOffset; + private int totalLength; private ConfidenceType confidence; private int highlightOffset; private int highlightLength; diff --git a/src/main/java/com/ssg/starroad/review/controller/ReviewController.java b/src/main/java/com/ssg/starroad/review/controller/ReviewController.java index 5bdc573..cf422b2 100644 --- a/src/main/java/com/ssg/starroad/review/controller/ReviewController.java +++ b/src/main/java/com/ssg/starroad/review/controller/ReviewController.java @@ -1,19 +1,16 @@ package com.ssg.starroad.review.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.ssg.starroad.common.service.S3Uploader; -import com.ssg.starroad.review.DTO.*; +import com.ssg.starroad.review.DTO.ResponseReviewDTO; +import com.ssg.starroad.review.DTO.ReviewDTO; +import com.ssg.starroad.review.DTO.ReviewReceiptDTO; +import com.ssg.starroad.review.DTO.ReviewRequestDTO; import com.ssg.starroad.review.entity.Review; -import com.ssg.starroad.review.entity.ReviewImage; import com.ssg.starroad.review.repository.ReviewImageRepository; import com.ssg.starroad.review.service.ReviewFeedbackService; import com.ssg.starroad.review.service.ReviewReceiptService; import com.ssg.starroad.review.service.ReviewService; -import com.ssg.starroad.shop.entity.Store; -import com.ssg.starroad.shop.repository.StoreRepository; import com.ssg.starroad.user.dto.RankUserDTO; -import com.ssg.starroad.user.entity.User; -import com.ssg.starroad.user.repository.UserRepository; import com.ssg.starroad.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -22,7 +19,6 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.util.ArrayList; import java.util.List; @RequestMapping("/reviews") @@ -33,11 +29,7 @@ public class ReviewController { private final ReviewService reviewService; private final ReviewReceiptService reviewReceiptService; private final ReviewFeedbackService reviewFeedbackService; - private final StoreRepository storeRepository; - private final UserRepository userRepository; - private final S3Uploader s3Uploader; private final ReviewImageRepository imageRepository; - private final ReviewImageRepository reviewImageRepository; private final UserService userService; @@ -107,48 +99,9 @@ public ResponseEntity submitSurvey( // JSON 문자열을 ReviewDTO 객체로 변환합니다. ReviewDTO reviewDTO = new ObjectMapper().readValue(reviewStr, ReviewDTO.class); - System.out.println("reviewSelectionDTO toString : " + reviewDTO.toString()); - - User user = userRepository.findBynickname(reviewDTO.getUserNickname()).orElseThrow(() -> new IllegalArgumentException("User not found for the nickname: " + reviewDTO.getUserNickname())); - // Store 엔티티가 아직 저장되지 않은 경우 저장 - Store store = storeRepository.findByName(reviewDTO.getShopName()).orElseThrow(() -> new IllegalArgumentException("Store not found for the shop name: " + reviewDTO.getShopName())); - System.out.printf("Store Id : " + store.getId()); - // Review 엔티티 생성 및 저장 - Review savedReview = reviewService.createReview(Review.builder() - .user(user) - .store(store) - .paymentNum(reviewDTO.getPaymentNum()) - .contents(reviewDTO.getContents()) - .visible(true) - .likeCount(0L) - .build()); - Long reviewId = savedReview.getId(); - - List imageUrls = new ArrayList<>(); - if (uploadedImages != null && !uploadedImages.isEmpty()) { - imageUrls = s3Uploader.upload(uploadedImages.toArray(new MultipartFile[uploadedImages.size()]), "ssg/reviews"); - } - - System.out.println("================================================================"); - for (String imageUrl : imageUrls) { - System.out.println(imageUrl); - } - - // surveyData를 합쳐서 ReviewFeedbackDTO 생성 - String combinedSurveyData = reviewDTO.getCombinedSurveyData(); - ReviewFeedbackDTO reviewFeedbackDTO = ReviewFeedbackDTO.builder() - .reviewId(reviewId) - .reviewFeedbackSelection(combinedSurveyData) - .build(); - - imageUrls.forEach(url -> reviewImageRepository.save(ReviewImage.builder() - .review(savedReview) - .imagePath(url) - .build())); - - reviewFeedbackService.addReviewFeedback(reviewFeedbackDTO); + ResponseEntity result = reviewService.saveSurvey(reviewDTO, uploadedImages); - return ResponseEntity.ok("설문이 성공적으로 제출되었습니다!"); + return result; } @GetMapping("/rank") diff --git a/src/main/java/com/ssg/starroad/review/entity/ReviewKeyword.java b/src/main/java/com/ssg/starroad/review/entity/ReviewKeyword.java index c363598..f4cfc7c 100644 --- a/src/main/java/com/ssg/starroad/review/entity/ReviewKeyword.java +++ b/src/main/java/com/ssg/starroad/review/entity/ReviewKeyword.java @@ -22,4 +22,6 @@ public class ReviewKeyword { private Long id; private String name; + + private String storeType; } diff --git a/src/main/java/com/ssg/starroad/review/entity/ReviewSentiment.java b/src/main/java/com/ssg/starroad/review/entity/ReviewSentiment.java index df742c5..55bb83e 100644 --- a/src/main/java/com/ssg/starroad/review/entity/ReviewSentiment.java +++ b/src/main/java/com/ssg/starroad/review/entity/ReviewSentiment.java @@ -25,8 +25,8 @@ public class ReviewSentiment { private Review review; private String content; - private int offset; - private int length; + private int totalOffset; + private int totalLength; @Enumerated(EnumType.STRING) private ConfidenceType confidence; diff --git a/src/main/java/com/ssg/starroad/review/service/ReviewService.java b/src/main/java/com/ssg/starroad/review/service/ReviewService.java index 2d93371..d8692e1 100644 --- a/src/main/java/com/ssg/starroad/review/service/ReviewService.java +++ b/src/main/java/com/ssg/starroad/review/service/ReviewService.java @@ -1,6 +1,7 @@ package com.ssg.starroad.review.service; import com.ssg.starroad.review.DTO.ResponseReviewDTO; +import com.ssg.starroad.review.DTO.ReviewDTO; import com.ssg.starroad.review.entity.Review; import org.springframework.http.ResponseEntity; import org.springframework.web.multipart.MultipartFile; @@ -27,4 +28,8 @@ public interface ReviewService { ResponseReviewDTO findAllReview(String userEmail, int pageNo, int pageSize); ResponseReviewDTO findFollowingReview(String userEmail, int pageNo, int pageSize); + + String makeSummary(String content); + + ResponseEntity saveSurvey(ReviewDTO reviewDTO, List uploadedImages); } diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewFeedbackServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewFeedbackServiceImpl.java index 5f5851f..7e42063 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewFeedbackServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewFeedbackServiceImpl.java @@ -8,6 +8,7 @@ import com.ssg.starroad.review.repository.ReviewRepository; import com.ssg.starroad.review.service.ReviewFeedbackService; import com.ssg.starroad.review.service.ReviewSelectionService; +import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -39,15 +40,19 @@ public List getReviewFeedback(Long reviewId) { @Transactional @Override public ReviewFeedbackDTO addReviewFeedback(ReviewFeedbackDTO reviewFeedbackDTO) { + System.out.println("addReviewFeedBack 진입"); Long userId = reviewFeedbackDTO.getId(); - userRepository.findById(userId).ifPresent(user -> { - Integer reviewExp = user.getReviewExp(); - Integer point = user.getPoint(); - Integer additionalPoint = reviewExp < 100 ? 100 : reviewExp < 200 ? 200 : 300; - - userRepository.updatePointById(userId, point + additionalPoint); - userRepository.updateReviewExpById(userId, reviewExp + 10); - }); + User user = userRepository.findById(userId).get(); + + Integer reviewExp = user.getReviewExp(); + Integer additionalPoint = reviewExp < 100 ? 100 : reviewExp < 200 ? 200 : 300; + System.out.println("addReviewFeedBack User : " + user.toString()); + // 기존 User 엔티티를 업데이트합니다. + user.setReviewExp(user.getReviewExp() + 10); + user.setPoint(user.getPoint() + additionalPoint); + + // 변경된 User 엔티티를 저장합니다. + userRepository.save(user); // reviewFeedbackSelection을 ','를 기준으로 나눠 String 배열에 담는다. String[] selections = reviewFeedbackDTO.getReviewFeedbackSelection().split(","); diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewReceiptServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewReceiptServiceImpl.java index ac4ff32..573cd63 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewReceiptServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewReceiptServiceImpl.java @@ -6,6 +6,8 @@ import com.ssg.starroad.review.entity.ReviewReceipt; import com.ssg.starroad.review.repository.ReviewReceiptRepository; import com.ssg.starroad.review.service.ReviewReceiptService; +import com.ssg.starroad.shop.repository.StoreRepository; +import com.ssg.starroad.shop.service.StoreService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -23,6 +25,8 @@ public class ReviewReceiptServiceImpl implements ReviewReceiptService { private final ReviewReceiptRepository reviewReceiptRepository; + private final StoreRepository storeRepository; + private final StoreService storeService; @Override public ReviewReceiptDTO saveReviewReceipt(ReviewReceiptDTO reviewReceiptDTO) { @@ -46,7 +50,8 @@ public ReviewReceiptDTO processAndSaveOcrResult(String ocrResultJson) { JsonNode paymentInfoNode = rootNode.path("images").get(0).path("receipt").path("result").path("paymentInfo"); // 상점 이름 추출 -// String shopName = storeInfoNode.path("name").path("text").asText(); +// String shopName = storeService.findStoreName(storeInfoNode.path("name").path("text").asText()); +// storeRepository.findByName(shopName).orElse(null); String shopName = "노브랜드"; // 지점명 추출 - 필요하다면 String subName = storeInfoNode.path("subName").path("text").asText(); @@ -94,11 +99,12 @@ public ReviewReceiptDTO processAndSaveOcrResult(String ocrResultJson) { // savedReviewReceipt.getAddress(), // 주소 반환 - 필요한 경우 savedReviewReceipt.getPaymentType(), savedReviewReceipt.getApprovalNumber(), savedReviewReceipt.getPurchaseDate()); System.out.println("resultDTO : " + resultDTO.toString()); + return resultDTO; } catch (IOException e) { e.printStackTrace(); + // 예외 발생 시 적절한 예외를 던지거나 처리 + throw new RuntimeException("OCR 처리 중 오류 발생", e); } - - return resultDTO; } diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewSentimentServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewSentimentServiceImpl.java index 48dbf8d..e7ac345 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewSentimentServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewSentimentServiceImpl.java @@ -2,17 +2,18 @@ import com.ssg.starroad.review.DTO.ReviewSentimentDTO; import com.ssg.starroad.review.entity.ReviewSentiment; -import com.ssg.starroad.review.enums.ConfidenceType; import com.ssg.starroad.review.repository.ReviewRepository; import com.ssg.starroad.review.repository.ReviewSentimentRepository; import com.ssg.starroad.review.service.ReviewSentimentService; import lombok.RequiredArgsConstructor; +import lombok.ToString; import org.springframework.stereotype.Service; import java.util.Optional; @Service @RequiredArgsConstructor +@ToString public class ReviewSentimentServiceImpl implements ReviewSentimentService { private final ReviewSentimentRepository reviewSentimentRepository; private final ReviewRepository reviewRepository; @@ -23,8 +24,8 @@ public ReviewSentimentDTO createReviewSentiment(ReviewSentimentDTO reviewSentime .review(reviewRepository.findById(reviewSentimentDTO.getReviewId()) .orElseThrow(() -> new IllegalArgumentException("Review not found"))) .content(reviewSentimentDTO.getContent()) - .offset(reviewSentimentDTO.getOffset()) - .length(reviewSentimentDTO.getLength()) + .totalOffset(reviewSentimentDTO.getTotalOffset()) + .totalLength(reviewSentimentDTO.getTotalLength()) .confidence(reviewSentimentDTO.getConfidence()) .highlightOffset(reviewSentimentDTO.getHighlightOffset()) .highlightLength(reviewSentimentDTO.getHighlightLength()) @@ -67,8 +68,8 @@ private ReviewSentimentDTO convertToDTO(ReviewSentiment reviewSentiment) { .id(reviewSentiment.getId()) .reviewId(reviewSentiment.getReview().getId()) .content(reviewSentiment.getContent()) - .offset(reviewSentiment.getOffset()) - .length(reviewSentiment.getLength()) + .totalOffset(reviewSentiment.getTotalOffset()) + .totalLength(reviewSentiment.getTotalLength()) .confidence(reviewSentiment.getConfidence()) .highlightOffset(reviewSentiment.getHighlightOffset()) .highlightLength(reviewSentiment.getHighlightLength()) diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java index 0983e45..5efad89 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java @@ -1,18 +1,26 @@ package com.ssg.starroad.review.service.impl; -import com.ssg.starroad.review.DTO.ResponseReviewDTO; -import com.ssg.starroad.review.DTO.ReviewDTO; -import com.ssg.starroad.review.DTO.ReviewFeedbackDTO; -import com.ssg.starroad.review.DTO.ReviewImageDTO; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ssg.starroad.common.service.S3Uploader; +import com.ssg.starroad.common.service.clova.Sentiment; +import com.ssg.starroad.review.DTO.*; import com.ssg.starroad.review.entity.Review; +import com.ssg.starroad.review.entity.ReviewImage; import com.ssg.starroad.review.repository.ReviewFollowRepository; +import com.ssg.starroad.review.repository.ReviewImageRepository; import com.ssg.starroad.review.repository.ReviewLikeRepository; import com.ssg.starroad.review.repository.ReviewRepository; import com.ssg.starroad.review.service.ReviewFeedbackService; import com.ssg.starroad.review.service.ReviewImageService; +import com.ssg.starroad.review.service.ReviewSentimentService; import com.ssg.starroad.review.service.ReviewService; +import com.ssg.starroad.shop.entity.Store; +import com.ssg.starroad.shop.repository.StoreRepository; +import com.ssg.starroad.user.entity.User; import com.ssg.starroad.user.repository.UserRepository; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -37,12 +45,28 @@ public class ReviewServiceImpl implements ReviewService { private final ReviewRepository reviewRepository; private final ReviewFollowRepository ReviewFollowRepository; private final RestTemplate restTemplate; - private final ReviewImageService reviewImageService; // ReviewImageService 인젝션 + private final ReviewImageService reviewImageService; private final ReviewFeedbackService reviewFeedbackService; private final ReviewFollowRepository reviewFollowRepository; private final ReviewLikeRepository reviewLikeRepository; private final UserRepository userRepository; + private final ReviewImageRepository reviewImageRepository; + private final StoreRepository storeRepository; + private final S3Uploader s3Uploader; + private final Sentiment sentiment; + private final ReviewSentimentService reviewSentimentService; + + + @Value("${naver.openapi.ocr.secret-key}") + private String secretKey; + + @Value("${naver.openapi.summary.key}") + private String summaryKey; + + @Value("${naver.openapi.summary.key-id}") + private String summaryKeyID; + @Override public Long countReviewsByUserId(Long userId) { @@ -99,7 +123,7 @@ public ResponseEntity callOcrApi(MultipartFile imageFile) throws IOExcep // OCR API에 요청을 보낼 때 필요한 headers 설정 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); - headers.set("X-OCR-SECRET", "b1NCaWFVeEpKb0lIbGF3c0xKaHlZS0FnY0tudERDZHQ="); + headers.set("X-OCR-SECRET", secretKey); // 이미지 파일을 Base64 인코딩 String base64Image = Base64.getEncoder().encodeToString(imageFile.getBytes()); @@ -221,4 +245,129 @@ public ResponseReviewDTO findFollowingReview(String userEmail, int pageNo, int p .hasNext(reviewPage.hasNext()) .build(); } + + @Override + public String makeSummary(String content) { + System.out.println("makeSummary 진입"); + // OCR API 엔드포인트 + String summaryApiUrl = "https://naveropenapi.apigw.ntruss.com/text-summary/v1/summarize"; + + // OCR API에 요청을 보낼 때 필요한 headers 설정 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("X-NCP-APIGW-API-KEY-ID", summaryKeyID); + headers.set("X-NCP-APIGW-API-KEY", summaryKey); + + // 요청 바디 생성 + Map body = new HashMap<>(); + body.put("document", Map.of( + "title", "", + "content", content + )); + body.put("option", Map.of( + "language", "ko", + "model", "general", + "tone", 3, + "summaryCount", 3 + )); + + HttpEntity> entity = new HttpEntity<>(body, headers); + System.out.println("before makeSummary : " + entity); + // 텍스트 요약 API에 POST 요청 보내기 + ResponseEntity response = restTemplate.postForEntity(summaryApiUrl, entity, String.class); + System.out.println("makeSummary : " + response.getBody()); + + // JSON 응답 문자열 + ObjectMapper objectMapper = new ObjectMapper(); + String summary = null; + try { + // JSON 문자열을 JsonNode로 변환 + JsonNode rootNode = objectMapper.readTree(response.getBody()); + + // summary 값 추출 + JsonNode summaryNode = rootNode.path("summary"); + summary = summaryNode.asText(); + } catch (Exception e) { + e.printStackTrace(); + } + + // "summary" 키에 해당하는 값을 가져옴 + return summary; + } + + @Override + public ResponseEntity saveSurvey(ReviewDTO reviewDTO, List uploadedImages) { + System.out.println("reviewSelectionDTO toString : " + reviewDTO.toString()); + + User user = userRepository.findByEmail(reviewDTO.getUserEmail()).orElseThrow(() -> new IllegalArgumentException("User not found for the userEmail: " + reviewDTO.getUserEmail())); + System.out.println("sumbitSurvey user : " + user.toString()); + // Store 엔티티가 아직 저장되지 않은 경우 저장 + System.out.println("reviewDTO getShopName : " + reviewDTO.getShopName()); + Store store = storeRepository.findByName(reviewDTO.getShopName()).orElseThrow(() -> new IllegalArgumentException("Store not found for the shop name: " + reviewDTO.getShopName())); + System.out.printf("Store Id : " + store.getId()); + + String summary = makeSummary(reviewDTO.getContents()); + + SentimentDTO dto = sentiment.getSentiment(reviewDTO.getContents()); + + // Review 엔티티 생성 및 저장 + Review savedReview = createReview(Review.builder() + .user(user) + .store(store) + .confidence(dto.getDocumentConfidence()) + .paymentNum(reviewDTO.getPaymentNum()) + .contents(reviewDTO.getContents()) + .summary(summary) + .visible(true) + .likeCount(0L) + .build()); + + List sentimentDTOList = dto.getSentimentDetailDTOList().stream().map(x -> ReviewSentimentDTO.builder() + .reviewId(savedReview.getId()) + .content(x.getContent()) + .totalOffset(x.getOffset()) + .totalLength(x.getLength()) + .confidence(x.getConfidence()) + .highlightOffset(x.getHighlightOffset()) + .highlightLength(x.getHighlightLength()) + .build()).toList(); + + List reviewSentimentDTOList = sentimentDTOList.stream().map(reviewSentimentDTO -> { + System.out.println("reviewSentimentDTO : " + reviewSentimentDTO.toString()); + return reviewSentimentService.createReviewSentiment(reviewSentimentDTO); + }).toList(); + + System.out.println("리뷰 생성 완료"); + Long reviewId = savedReview.getId(); + System.out.println("submitSurvey reviewId : " + reviewId); + + List imageUrls = new ArrayList<>(); + if (uploadedImages != null && !uploadedImages.isEmpty()) { + imageUrls = s3Uploader.upload(uploadedImages.toArray(new MultipartFile[uploadedImages.size()]), "ssg/reviews"); + } + + System.out.println("================================================================"); + for (String imageUrl : imageUrls) { + System.out.println(imageUrl); + } + + // surveyData를 합쳐서 ReviewFeedbackDTO 생성 + String combinedSurveyData = reviewDTO.getCombinedSurveyData(); + ReviewFeedbackDTO reviewFeedbackDTO = ReviewFeedbackDTO.builder() + .id(user.getId()) + .reviewId(reviewId) + .reviewFeedbackSelection(combinedSurveyData) + .build(); + + System.out.println("submitSurvey reviewFeedbackDTO ID : " + reviewFeedbackDTO.getId()); + + imageUrls.forEach(url -> reviewImageRepository.save(ReviewImage.builder() + .review(savedReview) + .imagePath(url) + .build())); + + reviewFeedbackService.addReviewFeedback(reviewFeedbackDTO); + + return ResponseEntity.ok("설문이 성공적으로 제출되었습니다!"); + } } diff --git a/src/main/java/com/ssg/starroad/shop/service/StoreService.java b/src/main/java/com/ssg/starroad/shop/service/StoreService.java index d8f6395..6c87edc 100644 --- a/src/main/java/com/ssg/starroad/shop/service/StoreService.java +++ b/src/main/java/com/ssg/starroad/shop/service/StoreService.java @@ -2,15 +2,18 @@ import com.ssg.starroad.shop.DTO.StoreDTO; import com.ssg.starroad.shop.DTO.StoreWithReviewDTO; -import com.ssg.starroad.shop.entity.ComplexShoppingmall; -import com.ssg.starroad.shop.entity.Store; import java.util.List; public interface StoreService { - List searchStoreList(Long id); - StoreWithReviewDTO findStoreWithReview(Long storeId,String userEmail, int pageNo,int pageSize,String filter); - StoreDTO findStore(Long id); - String findStoreTypeByName(String name); + List searchStoreList(Long id); + + StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter); + + StoreDTO findStore(Long id); + + String findStoreTypeByName(String name); + + String findStoreName(String receiptShopName); } diff --git a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java index 7b30869..ecacba2 100644 --- a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java +++ b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java @@ -19,10 +19,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; -import org.springframework.data.domain.Pageable; import java.util.List; import java.util.stream.Collectors; @@ -37,9 +37,9 @@ public class StoreServiceImpl implements StoreService { private final ReviewService reviewService; - private final ReviewLikeRepository reviewLikeRepository; private final UserRepository userRepository; + @Override public List searchStoreList(Long id) { // 주어진 complexShoppingmallId로 매장 목록을 조회합니다. @@ -51,8 +51,9 @@ public List searchStoreList(Long id) { return storeDTOList; } + @Override - public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize,String filter) { + public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter) { // 주어진 storeId로 스토어를 조회합니다. Store store = storeRepository.findById(storeId).orElseThrow(() -> new RuntimeException("존재하지 않는 스토어입니다.")); // 페이지 요청 객체를 생성합니다. @@ -131,8 +132,6 @@ public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, in } - - @Override public StoreDTO findStore(Long id) { Store store = storeRepository.findById(id).orElseThrow(() -> new RuntimeException("존재하지 않은 매장입니다")); @@ -146,10 +145,9 @@ public StoreDTO findStore(Long id) { .build(); - - return storeDTO; } + @Override // 매장 이름을 이용해 매장을 조회하고 매장 유형을 반환하는 메소드 public String findStoreTypeByName(String name) { @@ -157,4 +155,11 @@ public String findStoreTypeByName(String name) { return store.getStoreType(); } + @Override + public String findStoreName(String receiptShopName) { + return storeRepository.findByName(receiptShopName) + .map(Store::getName) // Store 객체에서 상점 이름을 추출 + .orElseThrow(() -> new RuntimeException("Store with name '" + receiptShopName + "' not found")); + } + } \ No newline at end of file diff --git a/src/main/java/com/ssg/starroad/user/entity/User.java b/src/main/java/com/ssg/starroad/user/entity/User.java index 015cf98..5d26645 100644 --- a/src/main/java/com/ssg/starroad/user/entity/User.java +++ b/src/main/java/com/ssg/starroad/user/entity/User.java @@ -9,10 +9,7 @@ import jakarta.validation.constraints.Past; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -36,7 +33,8 @@ @NoArgsConstructor(access = PROTECTED) @AllArgsConstructor @Embeddable - +@Setter +@ToString public class User extends BaseTimeEntity implements UserDetails, OAuth2User { @@ -93,7 +91,9 @@ public String getName() { return this.providerId; // providerId를 반환 } - public String getUserName() {return this.name;} + public String getUserName() { + return this.name; + } // 제네릭 타입 A를 사용하여, OAuth2User 인터페이스에서 // 다양한 타입의 사용자 속성을 안전하게 가져오도록 설계(필수) @@ -190,8 +190,9 @@ public void updateProfile(String nickname, String password) { public void changeActiveStatus(ActiveStatus newStatus) { this.activeStatus = newStatus; } - public void setProfileimgPath(String path){ - this.imagePath=path; + + public void setProfileimgPath(String path) { + this.imagePath = path; } } diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index 7a53288..09c9a59 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -36,6 +36,7 @@ public class UserServiceImpl implements UserService { private final UrlBasedCorsConfigurationSource corsConfigurationSource; private final FollowRepository followRepository; + @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { Optional userOptional = userRepository.findByEmail(email); @@ -295,6 +296,7 @@ public String addFollowUser(String userName, String email) { User toUser = toUserOpt.get(); System.out.println("fromUser, toUser : " + fromUser + ", " + toUser); + System.out.println("toUser tostring : " + toUser.toString()); Optional existingFollow = followRepository.findByFromUserAndToUser(fromUser, toUser); From 739c95e59b408b4d8bcdca27607f9a054a222172 Mon Sep 17 00:00:00 2001 From: ekmbjh Date: Wed, 29 May 2024 18:38:33 +0900 Subject: [PATCH 39/49] =?UTF-8?q?=EB=88=84=EB=9D=BD=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ssg/starroad/review/DTO/SentimentDTO.java | 19 +++++++++++++++ .../review/DTO/SentimentDetailDTO.java | 23 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/main/java/com/ssg/starroad/review/DTO/SentimentDTO.java create mode 100644 src/main/java/com/ssg/starroad/review/DTO/SentimentDetailDTO.java diff --git a/src/main/java/com/ssg/starroad/review/DTO/SentimentDTO.java b/src/main/java/com/ssg/starroad/review/DTO/SentimentDTO.java new file mode 100644 index 0000000..9152ebd --- /dev/null +++ b/src/main/java/com/ssg/starroad/review/DTO/SentimentDTO.java @@ -0,0 +1,19 @@ +package com.ssg.starroad.review.DTO; + +import com.ssg.starroad.review.enums.ConfidenceType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class SentimentDTO { + ConfidenceType documentConfidence; + + List sentimentDetailDTOList; +} diff --git a/src/main/java/com/ssg/starroad/review/DTO/SentimentDetailDTO.java b/src/main/java/com/ssg/starroad/review/DTO/SentimentDetailDTO.java new file mode 100644 index 0000000..6ba4c01 --- /dev/null +++ b/src/main/java/com/ssg/starroad/review/DTO/SentimentDetailDTO.java @@ -0,0 +1,23 @@ +package com.ssg.starroad.review.DTO; + +import com.ssg.starroad.review.enums.ConfidenceType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class SentimentDetailDTO { + + private String content; + private int offset; + private int length; + + private ConfidenceType confidence; + + private int highlightOffset; + private int highlightLength; +} From 48ec0aa0400d83f3ab991ff3d563e99d1784bf23 Mon Sep 17 00:00:00 2001 From: HRiver Date: Wed, 29 May 2024 20:53:29 +0900 Subject: [PATCH 40/49] hotfix : fix review merge part --- .../ssg/starroad/review/DTO/SentimentDTO.java | 19 +++++++++++++++ .../review/DTO/SentimentDetailDTO.java | 23 +++++++++++++++++++ .../service/impl/ReviewServiceImpl.java | 4 +--- .../starroad/shop/service/StoreService.java | 2 +- .../shop/service/impl/StoreServiceImpl.java | 2 +- 5 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/ssg/starroad/review/DTO/SentimentDTO.java create mode 100644 src/main/java/com/ssg/starroad/review/DTO/SentimentDetailDTO.java diff --git a/src/main/java/com/ssg/starroad/review/DTO/SentimentDTO.java b/src/main/java/com/ssg/starroad/review/DTO/SentimentDTO.java new file mode 100644 index 0000000..9152ebd --- /dev/null +++ b/src/main/java/com/ssg/starroad/review/DTO/SentimentDTO.java @@ -0,0 +1,19 @@ +package com.ssg.starroad.review.DTO; + +import com.ssg.starroad.review.enums.ConfidenceType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class SentimentDTO { + ConfidenceType documentConfidence; + + List sentimentDetailDTOList; +} diff --git a/src/main/java/com/ssg/starroad/review/DTO/SentimentDetailDTO.java b/src/main/java/com/ssg/starroad/review/DTO/SentimentDetailDTO.java new file mode 100644 index 0000000..6ba4c01 --- /dev/null +++ b/src/main/java/com/ssg/starroad/review/DTO/SentimentDetailDTO.java @@ -0,0 +1,23 @@ +package com.ssg.starroad.review.DTO; + +import com.ssg.starroad.review.enums.ConfidenceType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Builder +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class SentimentDetailDTO { + + private String content; + private int offset; + private int length; + + private ConfidenceType confidence; + + private int highlightOffset; + private int highlightLength; +} diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java index d02713e..3f524c3 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java @@ -49,9 +49,7 @@ public class ReviewServiceImpl implements ReviewService { private final ReviewFeedbackService reviewFeedbackService; private final ReviewFollowRepository reviewFollowRepository; private final UserRepository userRepository; - private final ReviewLikeRepository reviewLikeRepository; - private final UserRepository userRepository; private final ReviewImageRepository reviewImageRepository; private final StoreRepository storeRepository; private final S3Uploader s3Uploader; @@ -370,7 +368,7 @@ public ResponseEntity saveSurvey(ReviewDTO reviewDTO, List searchStoreList(Long id); - StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter); + StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter,String sort); StoreDTO findStore(Long id); diff --git a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java index edaf9cc..b56fc2c 100644 --- a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java +++ b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java @@ -53,7 +53,7 @@ public List searchStoreList(Long id) { } @Override - public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter) { + public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter,String sort) { // 주어진 storeId로 스토어를 조회합니다. Store store = storeRepository.findById(storeId).orElseThrow(() -> new RuntimeException("존재하지 않는 스토어입니다.")); From 8536ebca6acda98a7eee9f3bff4edb3846290616 Mon Sep 17 00:00:00 2001 From: ekmbjh Date: Thu, 30 May 2024 11:05:51 +0900 Subject: [PATCH 41/49] =?UTF-8?q?follow=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=ED=94=84=EB=A1=A0=ED=8A=B8=20isfollowed?= =?UTF-8?q?=20undefined=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=A1=B0=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ReviewFollowRepository.java | 3 +- .../service/impl/ReviewServiceImpl.java | 4 +-- .../ssg/starroad/user/dto/RankUserDTO.java | 1 + .../user/repository/FollowRepository.java | 2 ++ .../user/service/impl/UserServiceImpl.java | 33 ++++++++++--------- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/ssg/starroad/review/repository/ReviewFollowRepository.java b/src/main/java/com/ssg/starroad/review/repository/ReviewFollowRepository.java index 31e2cd5..cd585da 100644 --- a/src/main/java/com/ssg/starroad/review/repository/ReviewFollowRepository.java +++ b/src/main/java/com/ssg/starroad/review/repository/ReviewFollowRepository.java @@ -1,6 +1,5 @@ package com.ssg.starroad.review.repository; -import com.ssg.starroad.review.entity.ReviewImage; import com.ssg.starroad.user.entity.Follow; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -12,4 +11,6 @@ public interface ReviewFollowRepository extends JpaRepository { @Query("SELECT f.toUser.id FROM Follow f WHERE f.fromUser.id = :fromUserId") List findToUserIdsByFromUserId(@Param("fromUserId") Long fromUserId); + + } diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java index d02713e..04eb852 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java @@ -51,7 +51,6 @@ public class ReviewServiceImpl implements ReviewService { private final UserRepository userRepository; private final ReviewLikeRepository reviewLikeRepository; - private final UserRepository userRepository; private final ReviewImageRepository reviewImageRepository; private final StoreRepository storeRepository; private final S3Uploader s3Uploader; @@ -370,8 +369,9 @@ public ResponseEntity saveSurvey(ReviewDTO reviewDTO, List reviewPage = reviewRepository.findAllByUserIds(Collections.singletonList(userId), pageable); diff --git a/src/main/java/com/ssg/starroad/user/dto/RankUserDTO.java b/src/main/java/com/ssg/starroad/user/dto/RankUserDTO.java index 52a5bc7..0110bc5 100644 --- a/src/main/java/com/ssg/starroad/user/dto/RankUserDTO.java +++ b/src/main/java/com/ssg/starroad/user/dto/RankUserDTO.java @@ -19,6 +19,7 @@ public class RankUserDTO { private int reviewExp; private int point; private ActiveStatus activeStatus; + private boolean isFollowed; public RankUserDTO(User user) { this.id = user.getId(); diff --git a/src/main/java/com/ssg/starroad/user/repository/FollowRepository.java b/src/main/java/com/ssg/starroad/user/repository/FollowRepository.java index 3023d0d..ef9f5a1 100644 --- a/src/main/java/com/ssg/starroad/user/repository/FollowRepository.java +++ b/src/main/java/com/ssg/starroad/user/repository/FollowRepository.java @@ -28,4 +28,6 @@ public interface FollowRepository extends JpaRepository { void deleteByFromUserIdAndToUserId(@Param("fromUserId") Long fromUserId, @Param("toUserId") Long toUserId); Optional findByFromUserAndToUser(User fromUser, User toUser); + + boolean existsByFromUserIdAndToUserId(Long fromUserId, Long toUserId); } diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index 09c9a59..be2cb86 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -211,40 +211,43 @@ public void deleteProfileimg(String email) { @Override public List getRankUser(String email) { List users = userRepository.findAll(); - - Long id = findByEmail(email).get().getId(); + Long currentUserId = findByEmail(email).get().getId(); // id 값과 동일한 값을 제외하고 나머지 사용자들 선택 List filteredUsers = users.stream() - .filter(user -> !user.getId().equals(id)) + .filter(user -> !user.getId().equals(currentUserId)) .collect(Collectors.toList()); // reviewExp 필드를 기준으로 상위 3명의 사용자만 선택 - List topUsers = users.stream() + List topUsers = filteredUsers.stream() .sorted(Comparator.comparingInt(User::getReviewExp).reversed()) .limit(3) .collect(Collectors.toList()); - // 상위 3명의 사용자 목록을 출력 또는 다른 작업 수행 topUsers.forEach(System.out::println); // User 객체를 RankUserDTO 객체로 변환 List rankUserDTOS = topUsers.stream() - .map(user -> RankUserDTO.builder() - .id(user.getId()) - .name(user.getName()) - .nickname(user.getNickname()) - .email(user.getEmail()) - .imagePath(user.getImagePath()) - .reviewExp(user.getReviewExp()) - .point(user.getPoint()) - .activeStatus(user.getActiveStatus()) - .build()) + .map(user -> { + boolean isFollowed = followRepository.existsByFromUserIdAndToUserId(currentUserId, user.getId()); + return RankUserDTO.builder() + .id(user.getId()) + .name(user.getName()) + .nickname(user.getNickname()) + .email(user.getEmail()) + .imagePath(user.getImagePath()) + .reviewExp(user.getReviewExp()) + .point(user.getPoint()) + .activeStatus(user.getActiveStatus()) + .isFollowed(isFollowed) + .build(); + }) .collect(Collectors.toList()); return rankUserDTOS; } + @Override public List getAllUser(String email) { List users = userRepository.findAll(); From f8f2066788a9572cff17a552b36d016cf0c198f4 Mon Sep 17 00:00:00 2001 From: choijh9023 Date: Thu, 30 May 2024 12:31:41 +0900 Subject: [PATCH 42/49] Keyword Filter : Create about keyword filter in StoreReview --- .../starroad/review/entity/ReviewKeyword.java | 4 +++ .../repository/ReviewKeywordRepository.java | 7 +++++ .../review/repository/ReviewRepository.java | 7 +++++ .../repository/ReviewSentimentRepository.java | 10 +++++++ .../shop/controller/StoreController.java | 23 ++++++++++++-- .../starroad/shop/service/StoreService.java | 5 ++-- .../shop/service/impl/StoreServiceImpl.java | 30 ++++++++++++++----- 7 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/ssg/starroad/review/entity/ReviewKeyword.java b/src/main/java/com/ssg/starroad/review/entity/ReviewKeyword.java index c363598..00b2ada 100644 --- a/src/main/java/com/ssg/starroad/review/entity/ReviewKeyword.java +++ b/src/main/java/com/ssg/starroad/review/entity/ReviewKeyword.java @@ -1,5 +1,6 @@ package com.ssg.starroad.review.entity; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; @@ -22,4 +23,7 @@ public class ReviewKeyword { private Long id; private String name; + + @Column(name = "store_type") // 이 부분이 필요할 수 있습니다. + private String storeType; } diff --git a/src/main/java/com/ssg/starroad/review/repository/ReviewKeywordRepository.java b/src/main/java/com/ssg/starroad/review/repository/ReviewKeywordRepository.java index 5a46d0b..0aaf80b 100644 --- a/src/main/java/com/ssg/starroad/review/repository/ReviewKeywordRepository.java +++ b/src/main/java/com/ssg/starroad/review/repository/ReviewKeywordRepository.java @@ -2,6 +2,13 @@ import com.ssg.starroad.review.entity.ReviewKeyword; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; public interface ReviewKeywordRepository extends JpaRepository { + @Query("SELECT rk FROM ReviewKeyword rk WHERE rk.storeType = :storeType") + List findAllByStoreType(@Param("storeType") String storeType); + } diff --git a/src/main/java/com/ssg/starroad/review/repository/ReviewRepository.java b/src/main/java/com/ssg/starroad/review/repository/ReviewRepository.java index 241b98d..242b661 100644 --- a/src/main/java/com/ssg/starroad/review/repository/ReviewRepository.java +++ b/src/main/java/com/ssg/starroad/review/repository/ReviewRepository.java @@ -42,4 +42,11 @@ public interface ReviewRepository extends JpaRepository { @Query("SELECT r FROM Review r JOIN ReviewFeedback rf ON r.id = rf.review.id WHERE r.store.id = :storeId AND rf.reviewFeedbackSelection = :selection") Page findAllWithPageByStoreIdAndReviewFeedbackSelection(@Param("storeId") Long storeId, @Param("selection") String selection, Pageable pageable); + // 리뷰 ID 목록을 기반으로 페이징하여 리뷰를 조회하는 메서드 추가 + Page findByIdIn(List ids, Pageable pageable); + + // 새 메소드 추가 + Page findByStoreIdAndContentsContaining(Long storeId, String keyword, Pageable pageable); + + } diff --git a/src/main/java/com/ssg/starroad/review/repository/ReviewSentimentRepository.java b/src/main/java/com/ssg/starroad/review/repository/ReviewSentimentRepository.java index 84be73a..3ebc883 100644 --- a/src/main/java/com/ssg/starroad/review/repository/ReviewSentimentRepository.java +++ b/src/main/java/com/ssg/starroad/review/repository/ReviewSentimentRepository.java @@ -2,6 +2,16 @@ import com.ssg.starroad.review.entity.ReviewSentiment; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; public interface ReviewSentimentRepository extends JpaRepository { + + @Query("SELECT rs FROM ReviewSentiment rs WHERE rs.review.store.id = :storeId AND rs.content LIKE %:keyword%") + List findAllByStoreIdAndKeyword(@Param("storeId") Long storeId, @Param("keyword") String keyword); } + + + diff --git a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java index 4d66c97..e2b063d 100644 --- a/src/main/java/com/ssg/starroad/shop/controller/StoreController.java +++ b/src/main/java/com/ssg/starroad/shop/controller/StoreController.java @@ -1,5 +1,6 @@ package com.ssg.starroad.shop.controller; +import com.ssg.starroad.review.entity.ReviewKeyword; import com.ssg.starroad.shop.DTO.StoreDTO; import com.ssg.starroad.shop.DTO.StoreWithReviewDTO; import com.ssg.starroad.shop.service.StoreService; @@ -19,6 +20,21 @@ public class StoreController { private static final Logger log = LoggerFactory.getLogger(StoreController.class); private final StoreService storeService; + + + @GetMapping("/{id}/keywords") + public ResponseEntity> getStoreKeywords(@PathVariable Long id) { + try { + List keywords = storeService.getKeywordsByStoreCategory(id); + return ResponseEntity.ok(keywords); + } catch (RuntimeException e) { + log.error("Failed to fetch keywords for store ID: {}", id, e); + return ResponseEntity.notFound().build(); + } + } + + + // 메인 매장 목록을 검색하는 요청을 처리하는 메소드입니다. @GetMapping("/main") public ResponseEntity> StoreSearch() { @@ -36,10 +52,11 @@ public ResponseEntity StoreGetWithReviewById( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String filter, - @RequestParam(required = false) String sort) { + @RequestParam(required = false) String sort, + @RequestParam(required = false) String keyword) { try { - log.info("Received request for store ID: {}, userEmail: {}, page: {}, size: {}, filter: {}, sort: {}", id, userEmail, page, size, filter, sort); - StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, userEmail, page, size, filter, sort); + log.info("Received request for store ID: {}, userEmail: {}, page: {}, size: {}, filter: {}, sort: {}, keyword: {}", id, userEmail, page, size, filter, sort, keyword); + StoreWithReviewDTO storeWithReviewDTO = storeService.findStoreWithReview(id, userEmail, page, size, filter, sort,keyword); return ResponseEntity.ok(storeWithReviewDTO); } catch (RuntimeException e) { log.error("리뷰가 있는 상점을 가져오는데 실패했습니다", e); diff --git a/src/main/java/com/ssg/starroad/shop/service/StoreService.java b/src/main/java/com/ssg/starroad/shop/service/StoreService.java index 4b88431..2628180 100644 --- a/src/main/java/com/ssg/starroad/shop/service/StoreService.java +++ b/src/main/java/com/ssg/starroad/shop/service/StoreService.java @@ -1,5 +1,6 @@ package com.ssg.starroad.shop.service; +import com.ssg.starroad.review.entity.ReviewKeyword; import com.ssg.starroad.shop.DTO.StoreDTO; import com.ssg.starroad.shop.DTO.StoreWithReviewDTO; import com.ssg.starroad.shop.entity.ComplexShoppingmall; @@ -8,9 +9,9 @@ import java.util.List; public interface StoreService { - + List getKeywordsByStoreCategory(Long storeId); List searchStoreList(Long id); - StoreWithReviewDTO findStoreWithReview(Long storeId,String userEmail, int pageNo,int pageSize,String filter,String sort); + StoreWithReviewDTO findStoreWithReview(Long storeId,String userEmail, int pageNo,int pageSize,String filter,String sort, String keyword); StoreDTO findStore(Long id); String findStoreTypeByName(String name); } diff --git a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java index 585e449..4bf5a4d 100644 --- a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java +++ b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java @@ -4,9 +4,9 @@ import com.ssg.starroad.review.DTO.ReviewFeedbackDTO; import com.ssg.starroad.review.DTO.ReviewImageDTO; import com.ssg.starroad.review.entity.Review; -import com.ssg.starroad.review.repository.ReviewImageRepository; -import com.ssg.starroad.review.repository.ReviewLikeRepository; -import com.ssg.starroad.review.repository.ReviewRepository; +import com.ssg.starroad.review.entity.ReviewKeyword; +import com.ssg.starroad.review.entity.ReviewSentiment; +import com.ssg.starroad.review.repository.*; import com.ssg.starroad.review.service.ReviewFeedbackService; import com.ssg.starroad.review.service.ReviewImageService; import com.ssg.starroad.review.service.ReviewService; @@ -35,11 +35,20 @@ public class StoreServiceImpl implements StoreService { private final ReviewImageService reviewImageService; // ReviewImageService 인젝션 private final ReviewFeedbackService reviewFeedbackService; private final ReviewService reviewService; + private final ReviewSentimentRepository reviewSentimentRepository; - - + private final ReviewKeywordRepository reviewKeywordRepository; private final ReviewLikeRepository reviewLikeRepository; private final UserRepository userRepository; + + + @Override + public List getKeywordsByStoreCategory(Long storeId) { + Store store = storeRepository.findById(storeId).orElseThrow(() -> new RuntimeException("존재하지 않는 스토어입니다.")); + + return reviewKeywordRepository.findAllByStoreType(store.getStoreType()); + } + @Override public List searchStoreList(Long id) { // 주어진 complexShoppingmallId로 매장 목록을 조회합니다. @@ -52,7 +61,7 @@ public List searchStoreList(Long id) { return storeDTOList; } @Override - public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter, String sort) { + public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter, String sort, String keyword) { // 주어진 storeId로 스토어를 조회합니다. Store store = storeRepository.findById(storeId).orElseThrow(() -> new RuntimeException("존재하지 않는 스토어입니다.")); @@ -70,8 +79,13 @@ public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, in // 스토어 ID와 필터 조건에 따라 리뷰 페이지를 조회합니다. Page reviewPage; if (filter == null || filter.isEmpty()) { - // 필터가 없으면 모든 리뷰를 조회합니다. - reviewPage = reviewRepository.findAllWithPageByStoreId(storeId, pageable); + if (keyword == null || keyword.isEmpty()) { + // 필터와 키워드가 없으면 모든 리뷰를 조회합니다. + reviewPage = reviewRepository.findAllWithPageByStoreId(storeId, pageable); + } else { + // 키워드가 있으면 Review의 contents 필드를 사용하여 리뷰를 조회합니다. + reviewPage = reviewRepository.findByStoreIdAndContentsContaining(storeId, keyword, pageable); + } } else { // 필터가 있으면 필터 조건에 맞는 리뷰만 조회합니다. reviewPage = reviewRepository.findAllWithPageByStoreIdAndReviewFeedbackSelection(storeId, filter, pageable); From a9c3e240d32edf684da8368272c259fa181bff7f Mon Sep 17 00:00:00 2001 From: HRiver Date: Thu, 30 May 2024 13:52:09 +0900 Subject: [PATCH 43/49] hotfix : fix review merge part --- .../com/ssg/starroad/shop/service/impl/StoreServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java index 1d76d24..edbcdae 100644 --- a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java +++ b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java @@ -178,7 +178,7 @@ public String findStoreTypeByName(String name) { return store.getStoreType(); } - @Override + public String findStoreName(String receiptShopName) { return storeRepository.findByName(receiptShopName) .map(Store::getName) // Store 객체에서 상점 이름을 추출 From 01f18fa3d32831189d451cc57ee42b273990951a Mon Sep 17 00:00:00 2001 From: ekmbjh Date: Thu, 30 May 2024 16:42:57 +0900 Subject: [PATCH 44/49] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=ED=8C=94=EB=A1=9C=EC=9A=B0=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=98=A4=EB=A5=98=20=EC=A1=B0=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ssg/starroad/user/service/impl/UserServiceImpl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index be2cb86..083de6d 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -218,10 +218,9 @@ public List getRankUser(String email) { .filter(user -> !user.getId().equals(currentUserId)) .collect(Collectors.toList()); - // reviewExp 필드를 기준으로 상위 3명의 사용자만 선택 + // reviewExp 필드를 기준으로 내림차순 정렬 List topUsers = filteredUsers.stream() .sorted(Comparator.comparingInt(User::getReviewExp).reversed()) - .limit(3) .collect(Collectors.toList()); topUsers.forEach(System.out::println); From 0a27ff8b9fb3fed58bcddd9349bb6dafb269dc14 Mon Sep 17 00:00:00 2001 From: HRiver Date: Thu, 30 May 2024 22:28:07 +0900 Subject: [PATCH 45/49] hotfix : fix permit about password --- src/main/java/com/ssg/starroad/config/WebSecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java index aa6b9c6..48bab59 100644 --- a/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java +++ b/src/main/java/com/ssg/starroad/config/WebSecurityConfig.java @@ -60,11 +60,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionManagement(sessionManagementConfig -> sessionManagementConfig .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers("/user/login","/login/**","/user/logout","/user/join/**","/oauth2/**","/api/token/**","/auth/**").permitAll() + .requestMatchers("/user/login","/login/**","/user/logout","/user/join/**","/oauth2/**","/api/token/**","/auth/**","/user/update-password").permitAll() .requestMatchers("/api/oauth2/**").permitAll() .requestMatchers("/review-likes/**").permitAll() .requestMatchers("/store/**").permitAll() - .requestMatchers("/user/update-profile","/user/update-password","/user/details","/user/inactive").authenticated() + .requestMatchers("/user/update-profile","/user/details","/user/inactive").authenticated() .anyRequest().authenticated()) .oauth2Login(oauth -> oauth .loginPage("/user/login") From 5e8c528bf77c47e022821551f645737471a05711 Mon Sep 17 00:00:00 2001 From: choijh9023 Date: Fri, 31 May 2024 00:15:39 +0900 Subject: [PATCH 46/49] Follow : in StoreReview --- src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java | 2 +- .../com/ssg/starroad/shop/service/impl/StoreServiceImpl.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java b/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java index 30ab482..331a0df 100644 --- a/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java +++ b/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java @@ -21,6 +21,7 @@ public class ReviewDTO { private Long userId; private String userEmail; private String imagePath; + private boolean following; //팔로우 상태 private Long storeId; private boolean visible; private Long likeCount; @@ -32,7 +33,6 @@ public class ReviewDTO { private Long reviewcount; // 리뷰 갯수 private boolean isLiked; // 내가 추가한 필드 choims 24/05/23 private LocalDateTime createDate; - @Enumerated(EnumType.STRING) private ConfidenceType confidence; private String shopName; diff --git a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java index edbcdae..7f7649e 100644 --- a/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java +++ b/src/main/java/com/ssg/starroad/shop/service/impl/StoreServiceImpl.java @@ -15,6 +15,7 @@ import com.ssg.starroad.shop.entity.Store; import com.ssg.starroad.shop.repository.StoreRepository; import com.ssg.starroad.shop.service.StoreService; +import com.ssg.starroad.user.repository.FollowRepository; import com.ssg.starroad.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -41,6 +42,7 @@ public class StoreServiceImpl implements StoreService { private final ReviewKeywordRepository reviewKeywordRepository; private final ReviewLikeRepository reviewLikeRepository; private final UserRepository userRepository; + private final FollowRepository followRepository; @Override @@ -111,11 +113,14 @@ public StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, in List reviewFeedbackDTOs = reviewFeedbackService.getReviewFeedback(review.getId()); Long userReviewCount = reviewService.countReviewsByUserId(review.getUser().getId()); boolean isLiked = reviewLikeRepository.existsByUser_IdAndReview_Id(userId, review.getId()); + boolean following = followRepository.existsByFromUserIdAndToUserId(userId, review.getUser().getId()); return ReviewDTO.builder() .id(review.getId()) + .following(following) .userId(review.getUser().getId()) .userNickname(review.getUser().getNickname()) + .userEmail(review.getUser().getEmail()) .imagePath(review.getUser().getImagePath()) .storeId(review.getStore().getId()) .visible(review.isVisible()) From 8a3abd1f122dae0f666032454490e4674eed03c5 Mon Sep 17 00:00:00 2001 From: HRiver Date: Fri, 31 May 2024 11:36:12 +0900 Subject: [PATCH 47/49] hotfix : fix account withdraw --- .../java/com/ssg/starroad/user/service/impl/UserServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java index 083de6d..470c0db 100644 --- a/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java +++ b/src/main/java/com/ssg/starroad/user/service/impl/UserServiceImpl.java @@ -194,6 +194,7 @@ public void inactiveUser(String email) { .orElseThrow(() -> new UsernameNotFoundException("User not found")); user.changeActiveStatus(ActiveStatus.INACTIVE); + userRepository.save(user); } public String getProfileimg(String email) { From 6abfb836402ec80c4c205e1b0e8f68943f5fa16c Mon Sep 17 00:00:00 2001 From: ekmbjh Date: Fri, 31 May 2024 15:27:24 +0900 Subject: [PATCH 48/49] =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=A7=A4=EC=9E=A5?= =?UTF-8?q?=20=EC=A0=95=EB=B3=B4=20=EC=B6=94=EA=B0=80=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EB=B0=8F=20=EC=A0=84=EC=B2=B4=EB=B3=B4=EA=B8=B0=20=EC=A0=95?= =?UTF-8?q?=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ssg/starroad/review/DTO/ReviewDTO.java | 2 ++ .../service/impl/ReviewReceiptServiceImpl.java | 6 +++--- .../review/service/impl/ReviewServiceImpl.java | 10 ++++++++-- .../ssg/starroad/shop/service/StoreService.java | 16 +++++++++++----- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java b/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java index 30ab482..d3c0dd8 100644 --- a/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java +++ b/src/main/java/com/ssg/starroad/review/DTO/ReviewDTO.java @@ -1,6 +1,7 @@ package com.ssg.starroad.review.DTO; import com.ssg.starroad.review.enums.ConfidenceType; +import com.ssg.starroad.shop.entity.Store; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import lombok.*; @@ -35,6 +36,7 @@ public class ReviewDTO { @Enumerated(EnumType.STRING) private ConfidenceType confidence; + private Store store; private String shopName; private String purchaseDate; private String selectedTime; diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewReceiptServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewReceiptServiceImpl.java index 573cd63..1d3395b 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewReceiptServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewReceiptServiceImpl.java @@ -50,9 +50,9 @@ public ReviewReceiptDTO processAndSaveOcrResult(String ocrResultJson) { JsonNode paymentInfoNode = rootNode.path("images").get(0).path("receipt").path("result").path("paymentInfo"); // 상점 이름 추출 -// String shopName = storeService.findStoreName(storeInfoNode.path("name").path("text").asText()); -// storeRepository.findByName(shopName).orElse(null); - String shopName = "노브랜드"; + String shopName = storeService.findStoreName(storeInfoNode.path("name").path("text").asText()); + storeRepository.findByName(shopName).orElse(null); +// String shopName = "노브랜드"; // 지점명 추출 - 필요하다면 String subName = storeInfoNode.path("subName").path("text").asText(); // 주소 추출 - 배열의 첫 번째 주소 추출 diff --git a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java index 73cc048..f84777a 100644 --- a/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java +++ b/src/main/java/com/ssg/starroad/review/service/impl/ReviewServiceImpl.java @@ -157,7 +157,7 @@ public ResponseEntity callOcrApi(MultipartFile imageFile) throws IOExcep @Override public ResponseReviewDTO findAllReview(String userEmail, int pageNo, int pageSize) { // 최신순으로 정렬된 Pageable 객체 생성 - Pageable pageable = PageRequest.of(pageNo, pageSize, Sort.by(Sort.Direction.DESC, "createdAt")); + Pageable pageable = PageRequest.of(pageNo, pageSize, Sort.by(Sort.Direction.DESC, "likeCount")); // 정렬된 Pageable 객체를 사용하여 리뷰 페이지 가져오기 Page reviewPage = reviewRepository.findAll(pageable); Long userId = userRepository.findByEmail(userEmail).orElseThrow(() -> new RuntimeException("사용자를 찾을 수 없습니다.")).getId(); @@ -185,9 +185,14 @@ public ResponseReviewDTO findAllReview(String userEmail, int pageNo, int pageSiz .reviewcount(userReviewCount) .reviewImages(reviewImageDTOs) .reviewFeedbacks(reviewFeedbackDTOs) + .store(review.getStore()) .build(); }) .collect(Collectors.toList()); + List sortedList = reviewDTOList.stream() + .sorted(Comparator.comparingLong(ReviewDTO::getLikeCount).reversed()) + .collect(Collectors.toList()); + return ResponseReviewDTO.builder() .reviews(reviewDTOList) .pageNumber(reviewPage.getNumber()) @@ -234,6 +239,7 @@ public ResponseReviewDTO findFollowingReview(String userEmail, int pageNo, int p .reviewImages(reviewImageDTOs) .reviewFeedbacks(reviewFeedbackDTOs) .isLiked(isLiked) + .store(review.getStore()) .build(); }) .collect(Collectors.toList()); @@ -370,7 +376,7 @@ public ResponseEntity saveSurvey(ReviewDTO reviewDTO, List getKeywordsByStoreCategory(Long storeId); - List searchStoreList(Long id); - StoreWithReviewDTO findStoreWithReview(Long storeId,String userEmail, int pageNo,int pageSize,String filter,String sort, String keyword); - StoreDTO findStore(Long id); - String findStoreTypeByName(String name); + List getKeywordsByStoreCategory(Long storeId); + + List searchStoreList(Long id); + + StoreWithReviewDTO findStoreWithReview(Long storeId, String userEmail, int pageNo, int pageSize, String filter, String sort, String keyword); + + StoreDTO findStore(Long id); + + String findStoreTypeByName(String name); + + String findStoreName(String name); } From 319ab808a7f2fde6cb78b9deaa7ab0f16e6c2289 Mon Sep 17 00:00:00 2001 From: Yang Seong Jun <100821691+JJuuuunn@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:22:22 +0900 Subject: [PATCH 49/49] refact : image resizing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit thumnailator를 사용하여 이미지 리사이징하도록 변환 --- build.gradle | 3 + .../starroad/common/service/S3Uploader.java | 74 +++++++++++++++---- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/build.gradle b/build.gradle index 07b03b4..297fced 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,9 @@ dependencies { // JSON implementation 'org.json:json:20231013' + // thumbnailator + implementation 'net.coobird:thumbnailator:0.4.14' + // TEST IMPLEMENTATION ======================== // TEST DATABASE H2 diff --git a/src/main/java/com/ssg/starroad/common/service/S3Uploader.java b/src/main/java/com/ssg/starroad/common/service/S3Uploader.java index 164dfd8..53093ef 100644 --- a/src/main/java/com/ssg/starroad/common/service/S3Uploader.java +++ b/src/main/java/com/ssg/starroad/common/service/S3Uploader.java @@ -5,10 +5,16 @@ import com.amazonaws.services.s3.model.ObjectMetadata; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import net.coobird.thumbnailator.Thumbnails; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -43,17 +49,30 @@ public String upload(MultipartFile multipartFile, String dirName) { String encodedFileName = null; encodedFileName = Base64.getUrlEncoder().encodeToString(fileName.getBytes(StandardCharsets.UTF_8)); + // S3에 저장될 파일명 구성 String s3FileName = dirName + "/" + UUID.randomUUID() + "_" + encodedFileName + "." + extension; ObjectMetadata objectMetadata = new ObjectMetadata(); + // 파일을 byte로 변환 + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { - objectMetadata.setContentLength(multipartFile.getInputStream().available()); + // Thumbnailator를 사용하여 이미지 리사이징 + Thumbnails.of(multipartFile.getInputStream()) + .size(800, 800) + .outputFormat(extension) + .toOutputStream(outputStream); + + byte[] resizedImageBytes = outputStream.toByteArray(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(resizedImageBytes); + + + objectMetadata.setContentLength(resizedImageBytes.length); objectMetadata.setContentType(multipartFile.getContentType()); // 권한 공개 objectMetadata.setHeader("x-amz-acl", "public-read"); - amazonS3.putObject(bucket, s3FileName, multipartFile.getInputStream(), objectMetadata); + amazonS3.putObject(bucket, s3FileName, inputStream, objectMetadata); } catch (IOException e) { log.error("S3 upload fail : " + fileName, e); return String.format("S3 upload fail: %s", multipartFile.getOriginalFilename()); @@ -72,34 +91,57 @@ public String upload(MultipartFile multipartFile, String dirName) { public List upload(MultipartFile[] multipartFile, String dirName) { List urlList = new ArrayList<>(); for (MultipartFile file : multipartFile) { - // 파일명과 확장자 분리 + // Separate file name and extension String originalFileName = file.getOriginalFilename(); int lastIndex = originalFileName.lastIndexOf('.'); String fileName = originalFileName.substring(0, lastIndex); String extension = originalFileName.substring(lastIndex + 1); - // 파일명 URL 인코딩 - String encodedFileName = null; - encodedFileName = Base64.getUrlEncoder().encodeToString(fileName.getBytes(StandardCharsets.UTF_8)); + // URL encode the file name + String encodedFileName = Base64.getUrlEncoder().encodeToString(fileName.getBytes(StandardCharsets.UTF_8)); - // S3에 저장될 파일명 구성 + // Construct the file name to be stored in S3 String s3FileName = dirName + "/" + UUID.randomUUID() + "_" + encodedFileName + "." + extension; ObjectMetadata objectMetadata = new ObjectMetadata(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { - objectMetadata.setContentLength(file.getInputStream().available()); -// objectMetadata.setContentType(file.getContentType()); - // 파일 타입 체크후 이미지로 변경 if (file.getContentType().contains("image")) { + // Resize the image using Thumbnailator + Thumbnails.of(file.getInputStream()) + .size(800, 800) + .outputFormat(extension) + .toOutputStream(outputStream); + + byte[] resizedImageBytes = outputStream.toByteArray(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(resizedImageBytes); + + objectMetadata.setContentLength(resizedImageBytes.length); objectMetadata.setContentType("image/jpeg"); - } else { - objectMetadata.setContentType(file.getContentType()); - } + // Set public-read permission + objectMetadata.setHeader("x-amz-acl", "public-read"); - // 권한 공개 - objectMetadata.setHeader("x-amz-acl", "public-read"); + amazonS3.putObject(bucket, s3FileName, inputStream, objectMetadata); + } else { + // Convert non-image file to a basic image (JPEG) and resize + BufferedImage bufferedImage = new BufferedImage(800, 800, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = bufferedImage.createGraphics(); + g2d.setColor(Color.WHITE); // Set background to white + g2d.fillRect(0, 0, 800, 800); + g2d.setColor(Color.BLACK); // Set text color to black + g2d.drawString("Converted to Image", 50, 400); + g2d.dispose(); + + ImageIO.write(bufferedImage, "jpg", outputStream); + byte[] imageBytes = outputStream.toByteArray(); + ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes); + + objectMetadata.setContentLength(imageBytes.length); + objectMetadata.setContentType("image/jpeg"); + objectMetadata.setHeader("x-amz-acl", "public-read"); - amazonS3.putObject(bucket, s3FileName, file.getInputStream(), objectMetadata); + amazonS3.putObject(bucket, s3FileName, inputStream, objectMetadata); + } } catch (IOException e) { log.error("S3 upload fail : " + file.getOriginalFilename(), e); urlList.add(String.format("S3 upload fail: %s", file.getOriginalFilename()));