diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..a39e78f Binary files /dev/null and b/.DS_Store differ diff --git a/.github/ISSUE_TEMPLATE/---feature-template.md b/.github/ISSUE_TEMPLATE/---feature-template.md new file mode 100644 index 0000000..271104b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---feature-template.md @@ -0,0 +1,33 @@ +--- +name: "\U0001F680 Feature Template" +about: 기능 추가에 대한 작업 사항을 적습니다 +title: "\U0001F680[Feature] - " +labels: '' +assignees: '' + +--- + +# 🚀 Feature - + + + +### 📝 Description + +--- + +설명을 작성해주세요 + + +### ✅ To Do List + +--- + +- [ ] 작업1 +- [ ] 작업2 + +### 📚 Etc + +--- + +특이사항 + diff --git a/.github/ISSUE_TEMPLATE/---refactor-template.md b/.github/ISSUE_TEMPLATE/---refactor-template.md new file mode 100644 index 0000000..135cca0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---refactor-template.md @@ -0,0 +1,32 @@ +--- +name: "\U0001F528 Refactor Template" +about: 리팩토링 관련 작업 사항을 적습니다 +title: "\U0001F528 Refactor - " +labels: '' +assignees: '' + +--- + +# 🔨 Refactor - + + +### 📝 Description + +--- + +리팩토링 작업 사항 + +### ✅ To Do List + +--- + +- [ ] 수정1 +- [ ] 수정2 + +### 📚 Etc + +--- + +특이사항 + + diff --git a/.github/ISSUE_TEMPLATE/-bug-report-template.md b/.github/ISSUE_TEMPLATE/-bug-report-template.md new file mode 100644 index 0000000..1efc9bd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/-bug-report-template.md @@ -0,0 +1,38 @@ +--- +name: "❌Bug Report Template" +about: 버그가 발생한 상황과 수정 내역을 적습니다 +title: "\U0001F41E Bug - " +labels: '' +assignees: '' + +--- + +# 🐞 Bug - + + + +### 🕵️‍♀️ Condition + +--- + +에러 발생 조건 및 대상 + +### 📝 Description + +--- + +에러 발생 상황 설명 + +### ✅ To Do List + +--- + +- [ ] 수정1 +- [ ] 수정2 + +### 📚 Etc + +--- + +특이사항 + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..07f14b2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,28 @@ +# 💡 PR Summary - + + +### 📝 Description + +--- + +작업 사항 + +### 🌲 Working Branch + +--- + +작업 브랜치 + +### 📖 Related Issues + +--- + +이슈 번호 + + +### 📚 Etc + +--- + +특이사항 + diff --git a/build.gradle b/build.gradle index d28dd05..f6de763 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,7 @@ 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-webflux' //json implementation group: 'org.json', name: 'json', version: '20220320' @@ -35,6 +36,9 @@ dependencies { //jwt implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1' + //coolsms + implementation 'net.nurigo:sdk:4.2.4' + //swagger openapi implementation 'io.springfox:springfox-boot-starter:3.0.0' implementation 'io.springfox:springfox-swagger-ui:3.0.0' diff --git a/src/main/java/com/tikitaka/naechinso/NaechinsoApplication.java b/src/main/java/com/tikitaka/naechinso/NaechinsoApplication.java index deece24..ab72d71 100644 --- a/src/main/java/com/tikitaka/naechinso/NaechinsoApplication.java +++ b/src/main/java/com/tikitaka/naechinso/NaechinsoApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing //jpa entity 자동 감시 public class NaechinsoApplication { public static void main(String[] args) { diff --git a/src/main/java/com/tikitaka/naechinso/config/CommonApiResponse.java b/src/main/java/com/tikitaka/naechinso/config/CommonApiResponse.java index 01f021d..1f592f5 100644 --- a/src/main/java/com/tikitaka/naechinso/config/CommonApiResponse.java +++ b/src/main/java/com/tikitaka/naechinso/config/CommonApiResponse.java @@ -17,10 +17,21 @@ public class CommonApiResponse { private boolean success; private T data; + /** + * Http Response 를 내보낼 때 사용, 데이터를 Json 형식으로 만든다 + * default status code : 200 + * @param data 내보낼 데이터 + * */ public static CommonApiResponse of(T data) { return new CommonApiResponse<>(200, true, data); } + /** + * Http Response 를 내보낼 때 사용, 데이터를 Json 형식으로 만든다 + * 추가적으로 status code도 지정한다 + * @param data 내보낼 데이터 + * @param status 응답 status code 지정 + * */ public static CommonApiResponse of(T data, int status) { return new CommonApiResponse<>(status, true, data); } diff --git a/src/main/java/com/tikitaka/naechinso/config/RedisConfig.java b/src/main/java/com/tikitaka/naechinso/config/RedisConfig.java new file mode 100644 index 0000000..c686785 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/config/RedisConfig.java @@ -0,0 +1,40 @@ +package com.tikitaka.naechinso.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** Redis 설정입니다 + * @author gengminy (220812) */ +@Configuration +public class RedisConfig { + @Value("${REDIS_HOST}") + private String host; + + @Value("${REDIS_PORT}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate() { + // redisTemplate를 받아와서 set, get, delete를 사용 + RedisTemplate redisTemplate = new RedisTemplate<>(); + /** + * setKeySerializer, setValueSerializer 설정 + * redis-cli을 통해 직접 데이터를 조회 시 알아볼 수 없는 형태로 출력되는 것을 방지 + */ + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + + return redisTemplate; + } +} diff --git a/src/main/java/com/tikitaka/naechinso/config/SecurityConfig.java b/src/main/java/com/tikitaka/naechinso/config/SecurityConfig.java new file mode 100644 index 0000000..f26907f --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/config/SecurityConfig.java @@ -0,0 +1,97 @@ +package com.tikitaka.naechinso.config; + + +import lombok.RequiredArgsConstructor; +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.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.CorsUtils; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +/** 인증 및 Security 관련 설정 클래스입니다 + * @author gengminy (220728) */ +@EnableWebSecurity +@RequiredArgsConstructor +@Configuration +public class SecurityConfig { + private static final String[] SwaggerPatterns = { + "/swagger-resources/**", + "/swagger-ui.html", + "/v2/api-docs", + "/webjars/**" + }; +// private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; +// private final JwtAuthenticationFilter jwtAuthenticationFilter; +// private final JwtAccessDeniedHandler jwtAccessDeniedHandler; + + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .cors().configurationSource(corsConfigurationSource()) + .and() + .csrf().disable() + //예외처리 핸들러 + .exceptionHandling() +// .authenticationEntryPoint(jwtAuthenticationEntryPoint) +// .accessDeniedHandler(jwtAccessDeniedHandler) + .and() + .sessionManagement() + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .and() + .httpBasic().disable() + //권한이 필요한 요청에 대한 설정 + .authorizeRequests() + .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() + .antMatchers(SwaggerPatterns).permitAll() + .antMatchers("/admin/**").hasAuthority("ROLE_ADMIN") + .antMatchers("/user/**").authenticated() + .anyRequest().permitAll() + .and() + .headers().frameOptions().disable(); +// .and() +// .oauth2Login() +// .defaultSuccessUrl("/login-success") +// .successHandler(oAuth2AuthenticationSuccessHandler) +// .userInfoEndpoint() +// .userService(customOAuth2Service); + +// http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); + // 검토 필요 + } + + /** cors 설정 configuration bean */ + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + + //로컬 react 개발 환경 + configuration.addAllowedOriginPattern("*"); + //서버 react 프론트 환경 + configuration.addAllowedHeader("*"); + configuration.addAllowedMethod("*"); + configuration.addExposedHeader("x-auth-token"); + //내 서버의 응답 json 을 javascript에서 처리할수 있게 하는것(axios 등) + configuration.setAllowCredentials(true); + configuration.setMaxAge(3600L); + + source.registerCorsConfiguration("/**", configuration); + return source; + } + + /** 비밀번호 암호화 bcrypt Encoder 설정 */ + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} + diff --git a/src/main/java/com/tikitaka/naechinso/config/SwaggerConfig.java b/src/main/java/com/tikitaka/naechinso/config/SwaggerConfig.java index 66ca847..5e8f4f2 100644 --- a/src/main/java/com/tikitaka/naechinso/config/SwaggerConfig.java +++ b/src/main/java/com/tikitaka/naechinso/config/SwaggerConfig.java @@ -14,6 +14,9 @@ import java.util.*; +/** + * Swagger 사용 환경을 위한 설정 파일 + * */ @Configuration @EnableWebMvc public class SwaggerConfig { diff --git a/src/main/java/com/tikitaka/naechinso/config/WebClientConfig.java b/src/main/java/com/tikitaka/naechinso/config/WebClientConfig.java new file mode 100644 index 0000000..e6e0efe --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/config/WebClientConfig.java @@ -0,0 +1,42 @@ +package com.tikitaka.naechinso.config; + +import io.netty.channel.ChannelOption; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import java.time.Duration; +import java.util.concurrent.TimeUnit; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; + +/** + * Web Client 사용 환경을 위한 설정 파일 + * */ +@Configuration +public class WebClientConfig { + + @Bean + public WebClient webClient() { + HttpClient httpClient = HttpClient.create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) + .responseTimeout(Duration.ofMillis(5000)) + .doOnConnected(connection -> { + connection.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS)) + .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)); + }); + + WebClient webClient = WebClient.builder() + .defaultHeader(HttpHeaders.CONTENT_TYPE, String.valueOf(MediaType.APPLICATION_JSON)) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + + httpClient.warmup().block(); + + return webClient; + } + +} \ No newline at end of file diff --git a/src/main/java/com/tikitaka/naechinso/constant/DeleteStatus.java b/src/main/java/com/tikitaka/naechinso/constant/DeleteStatus.java new file mode 100644 index 0000000..0bde577 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/constant/DeleteStatus.java @@ -0,0 +1,5 @@ +package com.tikitaka.naechinso.constant; + +public enum DeleteStatus { + Y,N +} diff --git a/src/main/java/com/tikitaka/naechinso/controller/MessageController.java b/src/main/java/com/tikitaka/naechinso/controller/MessageController.java index 9a11c25..f1bf072 100644 --- a/src/main/java/com/tikitaka/naechinso/controller/MessageController.java +++ b/src/main/java/com/tikitaka/naechinso/controller/MessageController.java @@ -1,17 +1,33 @@ package com.tikitaka.naechinso.controller; +import com.fasterxml.jackson.core.JsonProcessingException; import com.tikitaka.naechinso.config.CommonApiResponse; +import com.tikitaka.naechinso.dto.MessageDTO; +import com.tikitaka.naechinso.dto.ResponseMessageDTO; import com.tikitaka.naechinso.exception.NotFoundException; +import com.tikitaka.naechinso.service.MessageService; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + @Slf4j @RestController +@RequiredArgsConstructor public class MessageController { - @GetMapping("/message") - public CommonApiResponse sendMessage(String content) { - return CommonApiResponse.of("메세지 전송 요청"); + private final MessageService messageService; + + @GetMapping("/send") + public CommonApiResponse sendMessage(String content) { + content = "인증번호는 1234 입니다"; + MessageDTO messageDTO = new MessageDTO("01028883492", content); + ResponseMessageDTO responseMessageDTO = messageService.sendMessage(messageDTO); + return CommonApiResponse.of(responseMessageDTO); } @GetMapping("/m") diff --git a/src/main/java/com/tikitaka/naechinso/controller/request/RequestMessage.java b/src/main/java/com/tikitaka/naechinso/controller/request/RequestMessage.java deleted file mode 100644 index e41b775..0000000 --- a/src/main/java/com/tikitaka/naechinso/controller/request/RequestMessage.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.tikitaka.naechinso.controller.request; - -public class RequestMessage { - String phoneNumber; - String content; -} diff --git a/src/main/java/com/tikitaka/naechinso/dto/MessageDTO.java b/src/main/java/com/tikitaka/naechinso/dto/MessageDTO.java new file mode 100644 index 0000000..dd6c653 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/dto/MessageDTO.java @@ -0,0 +1,17 @@ +package com.tikitaka.naechinso.dto; + +import lombok.*; + +/** + * 메세지 전송을 위한 DTO + * @author gengminy 22.09.10. + * */ +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Builder +@ToString +public class MessageDTO { + String to; + String content; +} diff --git a/src/main/java/com/tikitaka/naechinso/dto/RequestMessageDTO.java b/src/main/java/com/tikitaka/naechinso/dto/RequestMessageDTO.java new file mode 100644 index 0000000..246dd37 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/dto/RequestMessageDTO.java @@ -0,0 +1,19 @@ +package com.tikitaka.naechinso.dto; + +import lombok.*; + +import java.util.List; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Builder +@ToString +public class RequestMessageDTO { + String type; + String contentType; + String countryCode; + String from; + String content; + List messages; +} diff --git a/src/main/java/com/tikitaka/naechinso/dto/ResponseMessageDTO.java b/src/main/java/com/tikitaka/naechinso/dto/ResponseMessageDTO.java new file mode 100644 index 0000000..a531646 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/dto/ResponseMessageDTO.java @@ -0,0 +1,17 @@ +package com.tikitaka.naechinso.dto; + +import lombok.*; + +import java.time.LocalDateTime; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Builder +@ToString +public class ResponseMessageDTO { + String requestId; + LocalDateTime requestTime; + String statusCode; + String statusName; +} diff --git a/src/main/java/com/tikitaka/naechinso/entity/base/BaseEntity.java b/src/main/java/com/tikitaka/naechinso/entity/base/BaseEntity.java new file mode 100644 index 0000000..5dcf542 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/entity/base/BaseEntity.java @@ -0,0 +1,24 @@ +package com.tikitaka.naechinso.entity.base; + +import com.tikitaka.naechinso.constant.DeleteStatus; +import lombok.Getter; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; + +/** 상속받으면 생성시간 업데이트시간 + * 생성한 사람, 업데이트한 사람 필드 자동으로 만들어주는 엔티티입니다 + * jpa의 audit(감시) 기능을 사용합니다 */ +@MappedSuperclass +@EntityListeners({ AuditingEntityListener.class }) +@Getter +public class BaseEntity { + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "Text default 'N'") + private DeleteStatus delStatus=DeleteStatus.N; + + public void changeDeleteStatus(){ + if(this.delStatus ==DeleteStatus.Y){this.delStatus =DeleteStatus.N;} + else{this.delStatus=DeleteStatus.Y;} + } +} diff --git a/src/main/java/com/tikitaka/naechinso/entity/base/BaseTimeEntity.java b/src/main/java/com/tikitaka/naechinso/entity/base/BaseTimeEntity.java new file mode 100644 index 0000000..e23dfb1 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/entity/base/BaseTimeEntity.java @@ -0,0 +1,25 @@ +package com.tikitaka.naechinso.entity.base; + +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import java.time.LocalDateTime; + +/** 상속받으면 createdAt, updatedAt 필드 자동으로 만들어주는 엔티티입니다 + * jpa의 audit(감시) 기능을 사용합니다 */ +@MappedSuperclass +@EntityListeners({ AuditingEntityListener.class }) +@Getter +public class BaseTimeEntity extends BaseEntity{ + @CreatedDate + @Column(updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/tikitaka/naechinso/exception/BadRequestException.java b/src/main/java/com/tikitaka/naechinso/exception/BadRequestException.java index ad9e313..af607ab 100644 --- a/src/main/java/com/tikitaka/naechinso/exception/BadRequestException.java +++ b/src/main/java/com/tikitaka/naechinso/exception/BadRequestException.java @@ -2,10 +2,12 @@ import com.tikitaka.naechinso.constant.ErrorCode; import lombok.Getter; +import lombok.ToString; /** Status: 400 * Bad Request Error 반환하는 커스텀 클래스입니다 */ @Getter +@ToString public class BadRequestException extends BusinessException { private String message; diff --git a/src/main/java/com/tikitaka/naechinso/exception/ForbiddenException.java b/src/main/java/com/tikitaka/naechinso/exception/ForbiddenException.java index 5758d89..7870506 100644 --- a/src/main/java/com/tikitaka/naechinso/exception/ForbiddenException.java +++ b/src/main/java/com/tikitaka/naechinso/exception/ForbiddenException.java @@ -2,8 +2,10 @@ import com.tikitaka.naechinso.constant.ErrorCode; import lombok.Getter; +import lombok.ToString; @Getter +@ToString public class ForbiddenException extends BusinessException { private String message; diff --git a/src/main/java/com/tikitaka/naechinso/exception/InternalServerException.java b/src/main/java/com/tikitaka/naechinso/exception/InternalServerException.java index 028ddfa..2beda86 100644 --- a/src/main/java/com/tikitaka/naechinso/exception/InternalServerException.java +++ b/src/main/java/com/tikitaka/naechinso/exception/InternalServerException.java @@ -2,8 +2,10 @@ import com.tikitaka.naechinso.constant.ErrorCode; import lombok.Getter; +import lombok.ToString; @Getter +@ToString public class InternalServerException extends BusinessException { private String message; diff --git a/src/main/java/com/tikitaka/naechinso/exception/NotFoundException.java b/src/main/java/com/tikitaka/naechinso/exception/NotFoundException.java index b4626cb..959c040 100644 --- a/src/main/java/com/tikitaka/naechinso/exception/NotFoundException.java +++ b/src/main/java/com/tikitaka/naechinso/exception/NotFoundException.java @@ -2,8 +2,10 @@ import com.tikitaka.naechinso.constant.ErrorCode; import lombok.Getter; +import lombok.ToString; @Getter +@ToString public class NotFoundException extends BusinessException { private String message; diff --git a/src/main/java/com/tikitaka/naechinso/exception/UnauthorizedException.java b/src/main/java/com/tikitaka/naechinso/exception/UnauthorizedException.java index 12cacd9..a876aed 100644 --- a/src/main/java/com/tikitaka/naechinso/exception/UnauthorizedException.java +++ b/src/main/java/com/tikitaka/naechinso/exception/UnauthorizedException.java @@ -2,8 +2,10 @@ import com.tikitaka.naechinso.constant.ErrorCode; import lombok.Getter; +import lombok.ToString; @Getter +@ToString public class UnauthorizedException extends BusinessException { private String message; diff --git a/src/main/java/com/tikitaka/naechinso/service/AuthService.java b/src/main/java/com/tikitaka/naechinso/service/AuthService.java index 4cb603f..470cf53 100644 --- a/src/main/java/com/tikitaka/naechinso/service/AuthService.java +++ b/src/main/java/com/tikitaka/naechinso/service/AuthService.java @@ -1,4 +1,7 @@ package com.tikitaka.naechinso.service; -public class AuthService { +import org.springframework.stereotype.Service; + +@Service +public interface AuthService { } diff --git a/src/main/java/com/tikitaka/naechinso/service/AuthServiceImpl.java b/src/main/java/com/tikitaka/naechinso/service/AuthServiceImpl.java new file mode 100644 index 0000000..73adbf1 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/service/AuthServiceImpl.java @@ -0,0 +1,9 @@ +package com.tikitaka.naechinso.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class AuthServiceImpl implements AuthService { +} diff --git a/src/main/java/com/tikitaka/naechinso/service/MessageService.java b/src/main/java/com/tikitaka/naechinso/service/MessageService.java index 803ce73..0a0ee44 100644 --- a/src/main/java/com/tikitaka/naechinso/service/MessageService.java +++ b/src/main/java/com/tikitaka/naechinso/service/MessageService.java @@ -1,7 +1,19 @@ package com.tikitaka.naechinso.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.tikitaka.naechinso.dto.MessageDTO; +import com.tikitaka.naechinso.dto.ResponseMessageDTO; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; + +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; @Service -public class MessageService { +public interface MessageService { + String makeSignature(Long currentTime); + + ResponseMessageDTO sendMessage(MessageDTO messageDto); } diff --git a/src/main/java/com/tikitaka/naechinso/service/MessageServiceImpl.java b/src/main/java/com/tikitaka/naechinso/service/MessageServiceImpl.java new file mode 100644 index 0000000..9aa1783 --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/service/MessageServiceImpl.java @@ -0,0 +1,134 @@ +package com.tikitaka.naechinso.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.tikitaka.naechinso.constant.ErrorCode; +import com.tikitaka.naechinso.dto.MessageDTO; +import com.tikitaka.naechinso.dto.RequestMessageDTO; +import com.tikitaka.naechinso.dto.ResponseMessageDTO; +import com.tikitaka.naechinso.exception.BadRequestException; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.nurigo.sdk.message.model.Message; +import org.apache.commons.codec.binary.Base64; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; +import org.springframework.web.reactive.function.BodyInserter; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class MessageServiceImpl implements MessageService { + + private final WebClient webClient; + @Value("${NAVER_ACCESS_KEY}") + private String accessKey; + @Value("${NAVER_SECRET_KEY}") + private String secretKey; + @Value("${NAVER_SMS_ID}") + private String serviceId; + @Value("${NAVER_SMS_PHONE_NUMBER}") + private String senderNumber; + + @Override + public ResponseMessageDTO sendMessage(MessageDTO messageDto) { + final String smsURL = "https://sens.apigw.ntruss.com/sms/v2/services/"+ serviceId +"/messages"; + + try { + Long time = System.currentTimeMillis(); + + List messages = new ArrayList<>(); + messages.add(messageDto); + + RequestMessageDTO requestMessageDTO = RequestMessageDTO.builder() + .type("SMS") + .contentType("COMM") + .countryCode("82") + .from(senderNumber) + .content(messageDto.getContent()) + .messages(messages) + .build(); + + ObjectMapper objectMapper = new ObjectMapper(); + String body = objectMapper.writeValueAsString(requestMessageDTO); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("x-ncp-apigw-timestamp", time.toString()); + headers.set("x-ncp-iam-access-key", accessKey); + headers.set("x-ncp-apigw-signature-v2", makeSignature(time)); + + WebClient.ResponseSpec responseSpec = webClient.post().uri(smsURL) + .contentType(MediaType.APPLICATION_JSON) + .header("x-ncp-apigw-timestamp", time.toString()) + .header("x-ncp-iam-access-key", accessKey) + .header("x-ncp-apigw-signature-v2", makeSignature(time)) + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(body)) + .retrieve(); + + ResponseMessageDTO responseMessageDTO + = responseSpec.bodyToMono(ResponseMessageDTO.class).block(); + + return responseMessageDTO; + } catch (Exception e) { + e.printStackTrace(); + throw new BadRequestException(ErrorCode._BAD_REQUEST, "메세지 발송에 실패하였습니다"); + } + } + + /** + * 헤더에 서명 추가 + * */ + @Override + public String makeSignature(Long currentTime) { + String space = " "; + String newLine = "\n"; + String method = "POST"; + String url = "/sms/v2/services/" + this.serviceId + "/messages"; + String timestamp = currentTime.toString(); + String accessKey = this.accessKey; + String secretKey = this.secretKey; + + try { + + String message = new StringBuilder() + .append(method) + .append(space) + .append(url) + .append(newLine) + .append(timestamp) + .append(newLine) + .append(accessKey) + .toString(); + + SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(signingKey); + + byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8")); + String encodeBase64String = Base64.encodeBase64String(rawHmac); + + return encodeBase64String; + } catch (Exception e) { + throw new BadRequestException(ErrorCode._BAD_REQUEST, e.getMessage()); + } + } +} diff --git a/src/main/java/com/tikitaka/naechinso/service/RedisService.java b/src/main/java/com/tikitaka/naechinso/service/RedisService.java new file mode 100644 index 0000000..877c75e --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/service/RedisService.java @@ -0,0 +1,16 @@ +package com.tikitaka.naechinso.service; + +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +@Service +public interface RedisService { + void setValues(String key, String data); + + void setValues(String key, String data, Duration duration); + + String getValues(String key); + void deleteValues(String key); +} diff --git a/src/main/java/com/tikitaka/naechinso/service/RedisServiceImpl.java b/src/main/java/com/tikitaka/naechinso/service/RedisServiceImpl.java new file mode 100644 index 0000000..a9b220d --- /dev/null +++ b/src/main/java/com/tikitaka/naechinso/service/RedisServiceImpl.java @@ -0,0 +1,41 @@ +package com.tikitaka.naechinso.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +/** 레디스 관련 서비스입니다 + * @author gengminy (220812) */ +@Slf4j +@Service +@RequiredArgsConstructor +public class RedisServiceImpl implements RedisService { + private final RedisTemplate redisTemplate; + + @Override + public void setValues(String key, String data) { + ValueOperations values = redisTemplate.opsForValue(); + values.set(key, data); + } + + @Override + public void setValues(String key, String data, Duration duration) { + ValueOperations values = redisTemplate.opsForValue(); + values.set(key, data, duration); + } + + @Override + public String getValues(String key) { + ValueOperations values = redisTemplate.opsForValue(); + return values.get(key); + } + + @Override + public void deleteValues(String key) { + redisTemplate.delete(key); + } +}