Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] 이벤트유저 로그인 기능 구현 (#17) #22

Merged
merged 13 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.6'

// sms api
implementation 'net.nurigo:sdk:4.3.0'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static ResponseCommentDto from(Comment comment) {
return ResponseCommentDto.builder()
.id(comment.getId())
.content(comment.getContent())
.userName(comment.getEventUser().getUsername())
.userName(comment.getEventUser().getUserName())
.createdAt(comment.getCreatedAt().toString())
.build();
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/hyundai/softeer/orange/common/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public enum ErrorCode {
// 401 Unauthorized
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, false, "인증되지 않은 사용자입니다."),
AUTHENTICATION_FAILED(HttpStatus.UNAUTHORIZED, false, "아이디 또는 비밀번호가 일치하지 않습니다"),
INVALID_AUTH_CODE(HttpStatus.UNAUTHORIZED, false, "인증번호가 일치하지 않습니다."),
SESSION_EXPIRED(HttpStatus.UNAUTHORIZED, false, "세션이 만료되었습니다."),

// 403 Forbidden
FORBIDDEN(HttpStatus.FORBIDDEN, false, "권한이 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import hyundai.softeer.orange.admin.exception.AdminException;
import hyundai.softeer.orange.comment.exception.CommentException;
import hyundai.softeer.orange.eventuser.exception.EventUserException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
Expand Down Expand Up @@ -32,4 +33,9 @@ public ResponseEntity<Map<String, String>> handleInValidRequestException(MethodA
public ResponseEntity<ErrorResponse> handleCommentException(BaseException e) {
return ResponseEntity.status(e.getErrorCode().getHttpStatus()).body(ErrorResponse.from(e.getErrorCode()));
}

@ExceptionHandler(EventUserException.class)
public ResponseEntity<ErrorResponse> handleEventUserException(EventUserException e) {
return ResponseEntity.status(e.getErrorCode().getHttpStatus()).body(ErrorResponse.from(e.getErrorCode()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ public class ConstantUtil {

public static final String COMMENTS_KEY = "'comments'";
public static final String CLIENT_ID = "X-NCP-APIGW-API-KEY-ID";
public static final String CLIENT_SECRET = "X-NCP-APIGW-API-KEY";
public static final String CLIENT_SECRET = "X-NCP-APIGW-API-KEY";// 2시간
public static final String PHONE_NUMBER_REGEX = "010\\d{8}"; // 010 + 8자리 숫자
public static final String AUTH_CODE_REGEX = "\\d{6}"; // 6자리 숫자
public static final String AUTH_CODE_CREATE_REGEX = "%06d";
public static final String CLAIMS_KEY = "user";

public static final double LIMIT_NEGATIVE_CONFIDENCE = 99.5;
public static final int COMMENTS_SIZE = 20;
public static final int SCHEDULED_TIME = 1000 * 60 * 60 * 2; // 2시간
public static final int SCHEDULED_TIME = 1000 * 60 * 60 * 2;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MessageUtil {

public static final String BAD_INPUT = "잘못된 값이 입력되었거나 값이 누락되었습니다.";
public static final String BAD_INPUT = "null 혹은 빈 값이 입력되었습니다.";
public static final String OUT_OF_SIZE = "정해진 크기를 벗어났습니다.";
public static final String INVALID_PHONE_NUMBER = "올바르지 않은 전화번호 형식입니다.";
public static final String INVALID_AUTH_CODE = "인증번호는 숫자로만 입력해주세요.";
}
6 changes: 6 additions & 0 deletions src/main/java/hyundai/softeer/orange/config/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
Expand Down Expand Up @@ -39,6 +40,11 @@ public RedisTemplate<String, ResponseCommentsDto> redisTemplate(RedisConnectionF
return template;
}

@Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
return new StringRedisTemplate(connectionFactory);
}

@Bean
public CacheManager diareatCacheManager(RedisConnectionFactory cf) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package hyundai.softeer.orange.eventuser.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Data
@Configuration
@ConfigurationProperties(prefix = "cool-sms")
public class CoolSmsApiConfig {
private String apiKey;
private String apiSecret;
private String from;
private String url;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package hyundai.softeer.orange.eventuser.controller;

import hyundai.softeer.orange.common.dto.TokenDto;
import hyundai.softeer.orange.eventuser.dto.RequestAuthCodeDto;
import hyundai.softeer.orange.eventuser.dto.RequestUserDto;
import hyundai.softeer.orange.eventuser.service.EventUserService;
import hyundai.softeer.orange.eventuser.service.SmsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "EventUser", description = "EventUser 관련 API")
@RequiredArgsConstructor
@RequestMapping("/api/v1/event-user")
@RestController
public class EventUserController {

private final EventUserService eventUserService;
private final SmsService smsService;

// 로그인
@Tag(name = "EventUser")
@PostMapping("/login")
@Operation(summary = "로그인", description = "유저의 정보를 입력받아 로그인한다.", responses = {
@ApiResponse(responseCode = "200", description = "로그인 성공"),
@ApiResponse(responseCode = "400", description = "입력받은 정보의 유효성 검사가 실패했을 때"),
@ApiResponse(responseCode = "404", description = "해당 정보를 갖는 유저가 존재하지 않을 때")
})
public ResponseEntity<TokenDto> login(@RequestBody @Valid RequestUserDto dto) {
return ResponseEntity.ok(eventUserService.login(dto));
}

// 인증번호 전송
@Tag(name = "EventUser")
@PostMapping("/send-auth")
@Operation(summary = "인증번호 전송", description = "유저의 전화번호에 인증번호를 전송한다.", responses = {
@ApiResponse(responseCode = "200", description = "인증번호 전송 성공"),
@ApiResponse(responseCode = "400", description = "입력받은 정보의 유효성 검사가 실패했을 때")
})
public ResponseEntity<Void> sendAuthCode(@RequestBody @Valid RequestUserDto dto) {
smsService.sendSms(dto);
return ResponseEntity.ok().build();
}

// 인증번호 검증
@Tag(name = "EventUser")
@PostMapping("/check-auth")
@Operation(summary = "인증번호 검증", description = "유저가 입력한 인증번호를 검증한다.", responses = {
@ApiResponse(responseCode = "200", description = "인증번호 검증 성공"),
@ApiResponse(responseCode = "400", description = "입력받은 정보의 유효성 검사가 실패했을 때"),
@ApiResponse(responseCode = "401", description = "인증번호가 일치하지 않을 때")
})
public ResponseEntity<TokenDto> checkAuthCode(@RequestBody @Valid RequestAuthCodeDto dto) {
return ResponseEntity.ok(eventUserService.checkAuthCode(dto));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package hyundai.softeer.orange.eventuser.dto;

import hyundai.softeer.orange.common.util.ConstantUtil;
import hyundai.softeer.orange.common.util.MessageUtil;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class RequestAuthCodeDto {

@NotNull(message = MessageUtil.BAD_INPUT)
private String name;

@NotNull(message = MessageUtil.BAD_INPUT)
@Pattern(regexp = ConstantUtil.PHONE_NUMBER_REGEX, message = MessageUtil.INVALID_PHONE_NUMBER)
private String phoneNumber;

@NotNull
@Pattern(regexp = ConstantUtil.AUTH_CODE_REGEX, message = MessageUtil.INVALID_AUTH_CODE)
private String authCode;

@NotNull(message = MessageUtil.BAD_INPUT)
private Long eventFrameId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package hyundai.softeer.orange.eventuser.dto;

import hyundai.softeer.orange.common.util.ConstantUtil;
import hyundai.softeer.orange.common.util.MessageUtil;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Builder
@NoArgsConstructor
@Getter
public class RequestUserDto {

@NotNull(message = MessageUtil.BAD_INPUT)
private String name;

@NotNull(message = MessageUtil.BAD_INPUT)
@Pattern(regexp = ConstantUtil.PHONE_NUMBER_REGEX, message = MessageUtil.INVALID_PHONE_NUMBER)
private String phoneNumber;

public RequestUserDto(String name, String phoneNumber) {
this.name = name;
this.phoneNumber = phoneNumber;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ public class EventUser {
private Long id;

@Column
private String username;
private String userName;

@Column
private String phoneNumber;

@Column(unique = true, nullable = false)
private String userId;

@Column
private Integer score;

Expand All @@ -43,10 +46,11 @@ public class EventUser {
@OneToMany(mappedBy = "eventUser")
private List<FcfsEventWinningInfo> fcfsEventWinningInfoList = new ArrayList<>();

public static EventUser of(String username, String phoneNumber, EventFrame eventFrame) {
public static EventUser of(String userName, String phoneNumber, EventFrame eventFrame, String uuid) {
EventUser eventUser = new EventUser();
eventUser.username = username;
eventUser.userName = userName;
eventUser.phoneNumber = phoneNumber;
eventUser.userId = uuid;
eventUser.score = 0;
eventUser.eventFrame = eventFrame;
return eventUser;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package hyundai.softeer.orange.eventuser.exception;

import hyundai.softeer.orange.common.BaseException;
import hyundai.softeer.orange.common.ErrorCode;

public class EventUserException extends BaseException {

public EventUserException(ErrorCode errorCode) {
super(errorCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface EventUserRepository extends JpaRepository<EventUser, Long> {

Optional<EventUser> findByUserNameAndPhoneNumber(String userName, String phoneNumber);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package hyundai.softeer.orange.eventuser.service;

import hyundai.softeer.orange.common.util.ConstantUtil;
import hyundai.softeer.orange.eventuser.config.CoolSmsApiConfig;
import hyundai.softeer.orange.eventuser.dto.RequestUserDto;
import lombok.extern.slf4j.Slf4j;
import net.nurigo.sdk.NurigoApp;
import net.nurigo.sdk.message.model.Message;
import net.nurigo.sdk.message.request.SingleMessageSendingRequest;
import net.nurigo.sdk.message.response.SingleMessageSentResponse;
import net.nurigo.sdk.message.service.DefaultMessageService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Random;

@Slf4j
@Service
public class CoolSmsService implements SmsService {

private final DefaultMessageService defaultMessageService;
private final CoolSmsApiConfig coolSmsApiConfig;
private final StringRedisTemplate stringRedisTemplate;

public CoolSmsService(CoolSmsApiConfig coolSmsApiConfig, StringRedisTemplate stringRedisTemplate) {
this.defaultMessageService = NurigoApp.INSTANCE.initialize(coolSmsApiConfig.getApiKey(), coolSmsApiConfig.getApiSecret(), coolSmsApiConfig.getUrl());
this.coolSmsApiConfig = coolSmsApiConfig;
this.stringRedisTemplate = stringRedisTemplate;
}

@Override
public void sendSms(RequestUserDto dto) {
String authCode = generateAuthCode();
Message message = new Message();
message.setFrom(coolSmsApiConfig.getFrom());
message.setTo(dto.getPhoneNumber());
message.setText("[소프티어 오렌지] 인증번호는 (" + authCode + ")입니다.");

SingleMessageSentResponse response = defaultMessageService.sendOne(new SingleMessageSendingRequest(message));
log.info("{}에게 SMS 전송 완료: {}", dto.getPhoneNumber(), response);
stringRedisTemplate.opsForValue().set(dto.getPhoneNumber(), authCode);
}

// 6자리 난수 인증번호 생성
String generateAuthCode() {
return String.format(ConstantUtil.AUTH_CODE_CREATE_REGEX, new Random().nextInt(1000000));
}
}
Loading
Loading