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

[#31] Telegram Bot 기본 연동 및 회원가입 기능 구현 #32

Merged
merged 5 commits into from
Feb 18, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ public CommandLineRunner run(WebSocketManager webSocketManager) {
webSocketManager.startAll();
};
}

}
25 changes: 15 additions & 10 deletions api/src/main/java/com/whalewatch/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.whalewatch.service.UserService;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequestMapping("/api/users")
public class UserController {
Expand All @@ -23,25 +25,28 @@ public UserController(UserService userService,
this.jwtService = jwtService;
}

@PostMapping
public UserDto registerUser(@RequestBody UserDto userDto) {
User entity = userMapper.toEntity(userDto);
User saved = userService.registerUser(entity);
return userMapper.toDto(saved);
}

@PostMapping("/login")
public TokenResponseDto loginUser(@RequestBody UserDto userDto) {
// JwtService로 로그인 + 토큰 발급
return jwtService.login(userDto.getEmail(),userDto.getPassword());
public TokenResponseDto loginUser(@RequestBody Map<String, String> request) {
String email = request.get("email");
String otp = request.get("otp");
return jwtService.login(email, otp);
}

// 사용자의 이메일을 받아 OTP를 생성 후 텔레그램으로 전송
@PostMapping("/request-otp")
public String requestOtp(@RequestBody Map<String, String> request) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리턴 타입을 전부 동일하게 맞추는 것이 좋을 것 같습니다. 그래서 이 메소드도 클래스로 감싸서 결과를 내보내면 좋을 것 같네요.

String email = request.get("email");
userService.requestLoginOtp(email);
return "OTP has been sent to Telegram.";
}

@PostMapping("/refresh")
public TokenResponseDto refreshToken(@RequestBody TokenResponseDto tokenDto){
return jwtService.refreshAccessToken(tokenDto.getRefreshToken());
}

@GetMapping("{id}")
@GetMapping("/info/{id}")
public UserDto getUserInfo(@PathVariable int id) {
User user = userService.getUserInfo(id);
return userMapper.toDto(user);
Expand Down
5 changes: 5 additions & 0 deletions application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,8 @@ exchanges:
BTC: 0.8
ETH: 22.0
SOL: 300.0

telegram:
bot:
username: "${TELEGRAM_USERNAME}"
token: "${TELEGRAM_TOKEN}"
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// URL별 권한 설정
.authorizeHttpRequests(auth -> auth
.requestMatchers("/h2-console/**").permitAll()
.requestMatchers("/api/users", "/api/users/login").permitAll() // 회원가입 및 로그인 허용
.requestMatchers("/api/users/request-otp", "/api/users/login").permitAll() // 회원가입 및 로그인 허용
.requestMatchers("/api/users/**").authenticated()
.anyRequest().authenticated()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.whalewatch.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "telegram.bot")
public class TelegramBotProperties {
private String username;
private String token;

public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
11 changes: 1 addition & 10 deletions common/src/main/java/com/whalewatch/dto/UserDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ public class UserDto {
private int id;
private String email;
private String username;
private String password;

public UserDto(int id,String email, String username, String password) {
public UserDto(int id,String email, String username) {
this.id = id;
this.email = email;
this.username = username;
this.password = password;
}

public void setId(int id) {
Expand All @@ -25,10 +23,6 @@ public String getEmail() {
return email;
}

public String getPassword() {
return password;
}

public void setEmail(String email) {
this.email = email;
}
Expand All @@ -41,7 +35,4 @@ public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}
}
7 changes: 7 additions & 0 deletions service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'

testImplementation 'org.springframework.boot:spring-boot-starter-test'

//telegramBot
implementation 'org.telegram:telegrambots-spring-boot-starter:6.9.7.1'

//JAXB
implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.1'
}
30 changes: 20 additions & 10 deletions service/src/main/java/com/whalewatch/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,31 @@ public class User {

private String email;
private String username;
private String password;

private Long telegramChatId;
private String otpHash;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 필드는 일회용일테니, 필요하다면, 캐쉬같은 곳에 저장하면 좋을 것 같네요.


public Long getTelegramChatId() {
return telegramChatId;
}

public void setTelegramChatId(Long telegramChatId) {
this.telegramChatId = telegramChatId;
}

public String getOtpHash() {
return otpHash;
}

public void setOtpHash(String otpHash) {
this.otpHash = otpHash;
}

protected User() {}

public User(String email, String username, String password) {
public User(String email, String username) {
this.email = email;
this.username = username;
this.password = password;
}

public int getId() {
Expand All @@ -33,10 +50,6 @@ public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public void setEmail(String email) {
this.email = email;
}
Expand All @@ -45,7 +58,4 @@ public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}
}
2 changes: 0 additions & 2 deletions service/src/main/java/com/whalewatch/mapper/UserMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ public interface UserMapper {
@Mapping(target = "id", source = "id")
@Mapping(target = "email", source = "email")
@Mapping(target = "username", source = "username")
@Mapping(target = "password", source = "password")
UserDto toDto(User entity);

// DTO -> Entity
@Mapping(target = "id", ignore = true)
@Mapping(target = "email", source = "email")
@Mapping(target = "username", source = "username")
@Mapping(target = "password", source = "password")
User toEntity(UserDto dto);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,4 @@

public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByEmail(String email);

Optional<User> findByEmailAndPassword(String email, String password);
}
27 changes: 9 additions & 18 deletions service/src/main/java/com/whalewatch/service/JwtService.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,34 @@ public class JwtService {
private final JwtTokenRepository jwtTokenRepository;
private final JwtTokenProvider tokenProvider;
private final PasswordEncoder passwordEncoder;
private final UserService userService;

public JwtService(UserRepository userRepository,
JwtTokenRepository jwtTokenRepository,
JwtTokenProvider tokenProvider,
PasswordEncoder passwordEncoder) {
JwtTokenRepository jwtTokenRepository,
JwtTokenProvider tokenProvider,
PasswordEncoder passwordEncoder,
UserService userService) {
this.userRepository = userRepository;
this.jwtTokenRepository = jwtTokenRepository;
this.tokenProvider = tokenProvider;
this.passwordEncoder = passwordEncoder;
this.userService = userService;
}

public TokenResponseDto login(String email, String Password) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("Invalid email or password"));

if (!passwordEncoder.matches(Password, user.getPassword())) {
throw new RuntimeException("Invalid password");
}

public TokenResponseDto login(String email, String otp) {
User user = userService.loginWithOtp(email, otp);
String accessToken = tokenProvider.generateAccessToken(user.getEmail());
String refreshToken = tokenProvider.generateRefreshToken(user.getEmail());


return new TokenResponseDto(accessToken, refreshToken);
}

public TokenResponseDto refreshAccessToken(String refreshToken) {

// RefreshToken 자체가 유효한지
// RefreshToken 유효성 검사
if (!tokenProvider.validateToken(refreshToken)) {
throw new RuntimeException("Invalid or expired refresh token.");
}

// 새 Access Token 발급
String email = tokenProvider.getEmailFromToken(refreshToken);
String newAccessToken = tokenProvider.generateAccessToken(email);

return new TokenResponseDto(newAccessToken, refreshToken);
}
}
45 changes: 40 additions & 5 deletions service/src/main/java/com/whalewatch/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,63 @@

import com.whalewatch.domain.User;
import com.whalewatch.repository.UserRepository;
import com.whalewatch.telegram.TelegramMessageEvent;
import com.whalewatch.telegram.TelegramUserBot;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final ApplicationEventPublisher eventPublisher;

public UserService(UserRepository userRepository,
PasswordEncoder passwordEncoder) {
PasswordEncoder passwordEncoder,
ApplicationEventPublisher eventPublisher) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.eventPublisher = eventPublisher;
}

public User registerUser(User user) {
String hashed = passwordEncoder.encode(user.getPassword());
user.setPassword(hashed);

return userRepository.save(user);
}

public User getUserInfo(int id) {
return userRepository.findById(id).orElseThrow(() -> new RuntimeException("Not found"));
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Not found"));
}

// 이메일을 받아 OTP 생성 후, 해당 사용자의 otpHash 업데이트 및 텔레그램 메시지 전송 이벤트 발행
public void requestLoginOtp(String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found"));
String otp = String.valueOf((int) ((Math.random() * 900000) + 100000));
String otpHash = passwordEncoder.encode(otp);
user.setOtpHash(otpHash);
userRepository.save(user);

if (user.getTelegramChatId() != null) {
eventPublisher.publishEvent(new TelegramMessageEvent(user.getTelegramChatId(), "Your login OTP: " + otp));
} else {
throw new RuntimeException("User is not registered with Telegram");
}
}

// 입력받은 OTP가 저장된 otpHash와 일치하면 로그인 성공 처리
public User loginWithOtp(String email, String otp) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new RuntimeException("User not found"));
if (user.getOtpHash() != null && passwordEncoder.matches(otp, user.getOtpHash())) {
// OTP는 한 번 사용 후 삭제
user.setOtpHash(null);
userRepository.save(user);
return user;
} else {
throw new RuntimeException("Invalid OTP");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.whalewatch.telegram;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.telegram.telegrambots.meta.TelegramBotsApi;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.updatesreceivers.DefaultBotSession;

@Configuration
public class TelegramBotConfig {
@Bean
public TelegramBotsApi telegramBotsApi(TelegramUserBot telegramUserBot) throws TelegramApiException {
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
botsApi.registerBot(telegramUserBot);
return botsApi;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.whalewatch.telegram;

public class TelegramMessageEvent {
private final Long chatId;
private final String message;

public TelegramMessageEvent(Long chatId, String message) {
this.chatId = chatId;
this.message = message;
}

public Long getChatId() {
return chatId;
}

public String getMessage() {
return message;
}
}

Loading