반응형

이번 프로젝트에서 가장 중요했던 부분이다.

해당 질문에 대하여 학습된 답변을 가져와야 하는 데, 이 부분은 처음하는 거라 스케줄을 많이 빼고 충분히 볼 수 있도록 계획했었다.

 

일단 당연히 GCP의 dialogFlow가 필요하다.

기본적으로 질문: 안녕 -> 답변: 안녕하세요! 가 나오기 때문에 해당 답변을 얻어낼 수 있도록 해보자.

 

 

우선 설정에서 나오는 Project ID를 가져와야 한다.

 

해당 ID를 복사해서 application.yml에 넣어두자.

나중에 value로 사용해야 한다.

 

그러고 그 아이디를 클릭하면 GCP 콘솔로 넘어가게 된다.

여기서

 

라이브러리에 들어가서 

여기의 Dialogflow API에 들어간 후 사용할 수 있도록 설정을 해준다.

사실 굳이 안해도 이미 설정이 되어 있긴 하더라...

 

그리고 권한 설정을 위해

여기로 들어간다.

 

그러면 이렇게 계정들이 나올거다.

그러면 사용할 계정의 역할에 Dialogflow API 관리자를 추가해준다.

 

서비스 계정으로 넘어와서

나와있는 이메일을 클릭한 후

키를 생성해준다.

 

해당 키는 json 파일로 생성이 될 거고, 해당 키는 스프링부트에서 사용해야 하기 때문에 잘 저장해두도록 한다.

 

스프링부트로 넘어오자.

일단 다운받은 키를 설정해주어야 하는 데, 환경변수로

GOOGLE_APPLICATION_CREDENTIALS={키의 주소(.json 포함해서)}

 

키의 주소를 설정해주고 간다.

 

그리고 build.gradle에 해당 의존성을 추가 해준 후

implementation 'com.google.cloud:google-cloud-dialogflow:4.18.0'

 

private static int sessionId = 0;

    @Value("${dialogFlow.projectId}")
    private String projectId;

    @Override
    public ResponseEntity<String> getChat(String question) {
        try(SessionsClient sessionsClient = SessionsClient.create()){

            SessionName sessionName = SessionName.of(projectId, String.valueOf(sessionId++));

            TextInput.Builder textInput =
                    TextInput.newBuilder().setText(question).setLanguageCode("ko");

            QueryInput queryInput = QueryInput.newBuilder().setText(textInput).build();

            DetectIntentResponse response = sessionsClient.detectIntent(sessionName, queryInput);

            return new ResponseEntity<>(response.getQueryResult().getFulfillmentText(), HttpStatus.OK);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

 

나는 응답코드를 이렇게 작성했다.

 

요청받은 질문에 따라, 세션을 만들고 요청하는 방식이다.

이미 인증키가 있기 때문에 인증에도 문제가 생기지 않는다.

response.getQueryResult().getFulfillmentText()로 응답 중에서 답변을 가져올 수 있다.

 

간단하게 만든 API를 테스트 해보자면

이렇게 안녕을 입력했을 때 정상적인 답변이 나오게 된다.

하지만 좀 치명적인 문제가 많이 느리다는 것이다.

 

DetectIntentResponse response = sessionsClient.detectIntent(sessionName, queryInput);

 

 

세션의 아이디가 계속 바뀌기 때문에 다른 세션에서 동작을 하는 것으로 확인했고, 1개를 요청하던 2개를 요청하던 똑같은 시간으로 그냥 오래 걸린다.

 

당분간은 시간 줄일 수 있도록 해보고, 너무 느리다면 웹훅으로도 해보도록 해야겠다.

 

'크무톡톡 프로젝트' 카테고리의 다른 글

Nginx로 Swagger Proxy_pass  (1) 2024.07.22
CompletableFuture 적용으로 성능 튜닝  (0) 2024.03.09
SMTP 서버 구축  (0) 2024.01.04
EC2에 Java 17 설치  (1) 2023.12.29
EC2 memory swap  (0) 2023.12.29
반응형

최근에 서비스를 만들다보면 메일 서비스를 만들어야 하는 경우가 있었다.

앞으로도 쭉 사용을 할 거 같고 구글 메일에서 설정하는 부분을 자주 까먹어서 이번에 기록해두려고 한다.

 

  • 구글 이메일 설정

일단 서버를 구축하기 전에 구글 계정을 설정해주어야 한다.

 

구글의 계정설정 -> 보안

 

여기서 2단계 인증을 한 후 앱 비밀번호를 만들어준다.

 

나중에 스프링부트에서 사용해야 하기 때문에 이 때 만들어진 앱 비밀번호는 어디에다가 기록해두도록 한다.

 

그리고 Gmail 설정 -> 전달 및 POP/IMAP에서

IMAP 사용으로 변경 후 저장해준다.

 

  • SpringBoot 설정

이제 스프링부트에서 메일을 보낼 수 있는 서비스를 만들어보자.

 

일단 application.yml에 계정에 대한 정보를 입력한다.

 

spring.mail.host -> 서버 호스트

spring.mail.port -> 서버 포트 번호

spring.mail.username -> 서버 아이디

spring.mail.password -> 발급받은 앱 비밀번호 12자리

 

아래는 starttls 설정으로 안하면 관련된 에러가 발생할 것이다.

 

이제 전송하는 코드를 작성해보자.

일단 테스트 용으로 간단하게 API로 이메일을 받고 테스트 내용을 보내는 코드를 작성해 볼 것이다.

 

보낼 때는 javaMailSender를 이용한다.

 

@RestController
@RequiredArgsConstructor
class EmailController(
    val javaMailSender: JavaMailSender) {


    @GetMapping("/api/mail")
    fun send(email: String){
        val mimeMessage = javaMailSender.createMimeMessage()
        val helper = MimeMessageHelper(mimeMessage, true, "UTF-8")
        helper.setTo(email)
        helper.setSubject("테스트 용입니다.")
        helper.setText("테스트 드래곤입니다.")
        javaMailSender.send(mimeMessage)
    }
}

 

이렇게 작성을 끝내고 이제 Swagger로 테스트를 해보자.

 

 

이렇게 전송을 하면

 

 

잘 오는 것을 확인할 수 있다.

'크무톡톡 프로젝트' 카테고리의 다른 글

Nginx로 Swagger Proxy_pass  (1) 2024.07.22
CompletableFuture 적용으로 성능 튜닝  (0) 2024.03.09
Springboot와 DialogFlow 연동 - API  (0) 2024.01.17
EC2에 Java 17 설치  (1) 2023.12.29
EC2 memory swap  (0) 2023.12.29
반응형

저번 글에 Redis를 사용해 조회수를 구현했었다.

하지만 저번 로직은 가장 큰 문제가 있다.

해당 API를 계속 누르면 카운트가 계속 올라간다, 이렇게 만들면 한 유저가 그냥 페이지를 계속 새로고침 하면 조회수가 계속 올라가게 된다.

 

그렇기에 이것을 막기 위해 유저마다 조회수 올라가는 것에 대기 시간을 주기로 했다.

 

처음에는 HttpServletRequest에서 유저들의 IP를 받아오고 해당 IP들을 redis에 저장을 한 뒤 해당 IP가 존재하면 카운트를 하지 않는 방법을 생각했었다.

 

하지만 추천하지 않는 방법이라고 한다.

우선 사용자의 IP가 변할 수 있다. IP는 유동적으로 변할텐데, 만약 바뀐다면 같은 유저라도 카운트가 될 수 있기 때문이다.

그리고 Redis에 용량에 부담이 가게 되며, 유저들의 IP를 가지고 있는 것 또한 보안에 문제가 될 수 있다고 한다.

 

그래서 생각한 방법이 처음 접속을 하면 해당 유저에게 쿠키를 넣어주고 만약 쿠키를 체크했을 때 쿠키가 있다면 조회수를 증가시키지 않는 방법이다.

 

    public ResponseEntity<AllHitRes> hit(
            @ApiIgnore @CookieValue(value = "hitCookie", defaultValue = "0") Integer hitCookie,
            HttpServletResponse httpServletResponse){
        return allService.getHit(hitCookie, httpServletResponse);
    }

이렇게 Controller에서 쿠키를 가져온다.

 

    private final RedisTemplate<String, String> redisTemplate;

    @Transactional
    public ResponseEntity<AllHitRes> getHit(Integer hitCookie, HttpServletResponse httpServletResponse){


        if(hitCookie == 0) IncrementTodayAndSetCookie(httpServletResponse);

        Long today = Long.parseLong(Objects.requireNonNull(redisTemplate.opsForValue().get("today")));
        Long total = Long.parseLong(Objects.requireNonNull(redisTemplate.opsForValue().get("total")));

        return new ResponseEntity<>(new AllHitRes(today, today + total), HttpStatus.OK);
    }

    private void IncrementTodayAndSetCookie(HttpServletResponse httpServletResponse){
        redisTemplate.opsForValue().increment("today");

        Cookie cookie = new Cookie("hitCookie", "1");
        cookie.setMaxAge(900);
        httpServletResponse.addCookie(cookie);
    }

Service에서 체크를 하고 만약 hitCookie가 default 값인 0이라면 15분 유효의 쿠키를 넣어주고 today의 값을 1 증가시켜 준다.

 

이렇게 구현을 하니 쿠키를 직접 지우지 않는 이상 중복된 접속은 조회수가 증가를 하지 않게 되었다.

'블로그 개발 프로젝트' 카테고리의 다른 글

Redis를 사용한 조회수 구현  (0) 2023.08.10
Redis ERR value is not an integer or out of range  (0) 2023.08.09
Nginx에 페이지 연결하기  (0) 2023.08.07
EC2에 Nginx 초기 설정  (0) 2023.08.05
ExceptionHandler  (0) 2023.07.28
반응형

저번 글에서 작성한 것처럼 SpringBoot와 Redis를 연결하고 조회수 기능을 구현할 것이다.

 

저번에 RedisTemplate까지 작성을 했었다.

 

우선 계획은 오늘의 조회수와 총조회수를 반환하는 API를 만들고 해당 API를 호출할 때마다 오늘의 조회수가 +1 될 수 있도록 만들 것이다.

 

우선 조회수가 반환될 수 있는 Dto를 작성해준다.

@Data
@AllArgsConstructor
public class AllHitRes {

    @ApiModelProperty(
            value = "블로그 오늘 조회수",
            example = "1"
    )
    private Long today;

    @ApiModelProperty(
            value = "블로그 전체 조회수",
            example = "1020303"
    )
    private Long total;
}

 

그러고는 Redis에서 데이터를 가져오고 오늘의 조회수를 +1 해주는 Service를 구현해 보자.

    private final RedisTemplate<String, String> redisTemplate;

    @Transactional
    public ResponseEntity<AllHitRes> getHit(Integer hitCookie, HttpServletResponse httpServletResponse){
		
        redisTemplate.opsForValue().increment("today");

        Long today = Long.parseLong(Objects.requireNonNull(redisTemplate.opsForValue().get("today")));
        Long total = Long.parseLong(Objects.requireNonNull(redisTemplate.opsForValue().get("total")));
	
        return new ResponseEntity<>(new AllHitRes(today, today + total), HttpStatus.OK);
    }

 

increment를 이용하여 today를 +1 하고 그 today와 total을 가져온다.

값을 클라이언트로 보낼 때는 today와 today + total(금일의 조회수까지 합치기 위함)로 보낸다.

 

이제 하루에 한 번 redis에 모아둔 값을 DB로 보내야 한다.

이 방법은 자바의 스케줄러를 사용한다.

@Component
@AllArgsConstructor
@Slf4j
public class RedisToDbScheduler {

    private RedisTemplate<String, String> redisTemplate;
    private HitRepository hitRepository;

    @Scheduled(cron = "0 0 0 * * *")
    @Transactional
    public void saveHitToDB(){

        Long today = Long.parseLong(Objects.requireNonNull(redisTemplate.opsForValue().get("today")));
        Long total = Long.parseLong(Objects.requireNonNull(redisTemplate.opsForValue().get("total")));

        HitEntity hitEntity = new HitEntity(today, total);

        redisTemplate.opsForValue().set("total", String.valueOf(today + total));
        redisTemplate.opsForValue().set("today", String.valueOf(0));

        log.info("today: {}. total: {}", today, today + total);

        hitRepository.save(hitEntity);
    }
}

@Component를 달아주고 스케줄러로 사용할 메서드에 @Scheduled로 시간을 명시해 준다.

당연하게 @Component를 달아주어야 Spring에게 관리될 수 있다.

 

@Scheduled에는 cron을 사용하여 서버시간으로 0시 0분 0초에 해당 메서드가 실행될 수 있도록 만들었다.

저 메서드에서는 금일의 조회수와 총조회수를 가져와서 DB에 기록해 두고 기록해 두고

금일 조회수와 총조회수를 합쳐 총 조회수를 갱신을 한 후 금일 조회수를 다시 0으로 만들어준다.

 

이렇게 하면 하루에 한 번 메서드가 실행이 되어 redis와 db를 관리할 수 있다.

'블로그 개발 프로젝트' 카테고리의 다른 글

조회수 중복 유저 제거  (0) 2023.08.10
Redis ERR value is not an integer or out of range  (0) 2023.08.09
Nginx에 페이지 연결하기  (0) 2023.08.07
EC2에 Nginx 초기 설정  (0) 2023.08.05
ExceptionHandler  (0) 2023.07.28
반응형

블로그의 조회수를 저장하고 가져올 수 있도록 해야 할 거 같아서 해당 기능을 Redis로 사용해보려 했다.

 

당연히 redis를 build.gradle에 추가하고

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

RedisConfig를 작성해줬다.

package cobo.blog.global.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.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableRedisRepositories
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public RedisTemplate<?, ?> redisTemplate() {
        RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }
}

여기까지야 뭐... 그냥 다른 블로그들 찾아가며 작성했다.

 

당연히 괜찮을 줄 알았다....

 

이렇게 template을 작성하고

    public void RedisTest(){
        ValueOperations<String, Integer> stringIntegerValueOperations = redisTemplate.opsForValue();

        if(stringIntegerValueOperations.get("count") == null)
            stringIntegerValueOperations.set("count", 1);

        Long incrementedValue = stringIntegerValueOperations.increment("count");
        log.info("after increment: " + incrementedValue);
    }

이 코드를 실행해보았다.

 

하지만 이런 에러가 발생

난 분명히 1을 넣었는데, 증가할 수 없는 값이라고 한다.

 

redis로 바로 가서 확인해보았다.

내가 넣은 값이 이렇게 변환되어 저장이 되었고, 그렇기에 증가할 수 없었던 것이다.

 

이 부분을 바꾸고 싶다면 RedisTemplate을 다시 작성해야 한다.

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }

저장 하는 중에 Serialize하는 방법을 바꾸어 주면 해결이 된다.

 

    public void RedisTest(){
        ValueOperations<String, String> stringIntegerValueOperations = redisTemplate.opsForValue();

        if(stringIntegerValueOperations.get("count") == null)
            stringIntegerValueOperations.set("count", "1");

        Long incrementedValue = stringIntegerValueOperations.increment("count");
        log.info("after increment: " + incrementedValue);
    }

이렇게 코드르 수정하고 실행해보면 

잘 작동하는 것을 볼 수 있다.

'블로그 개발 프로젝트' 카테고리의 다른 글

조회수 중복 유저 제거  (0) 2023.08.10
Redis를 사용한 조회수 구현  (0) 2023.08.10
Nginx에 페이지 연결하기  (0) 2023.08.07
EC2에 Nginx 초기 설정  (0) 2023.08.05
ExceptionHandler  (0) 2023.07.28
반응형

페이지를 언제 받을지는 모르겠지만 일단 nginx를 이용한 초기설정은 끝내놓으려 한다.

(옛날에 nginx 그냥 키면 Hi! Nginx 이런 페이지가 보였던 거 같은데?)

일단 목표는 딱 거기까지 이다.

접속을 하면 만든 페이지가 보이지는 않더라도 다른 에러가 발생하지 않는것

 

일단 EC2에 접속해서 필요한 패키지들을 다운받자

sudo apt update
sudo apt install nginx

아마 nginx는 다운로드 하는 중간에 Y를 한 번 눌러줘야 할 것이다.

 

다운로드를 했다면 일단 nginx를 실행해준다.

sudo service nginx start

 

실행이 되었는지 확인하고 싶다면

sudo service nginx status

이 명령어를 입력했을 때 초록불이 나오는 지 확인하면 된다.

 

이제 포트를 제대로 잡았는 지 확인해보자.

nginx는 설정을 건드리지 않는다면 80번 포트를 사용하게 된다.

 

sudo lsof -i :80 -P -n

이 명령어를 입력했을 때 

이렇게 nginx가 잘 잡고 있는지를 확인해보자.

 

여기까지 왔다면 서버에서 할 설정은 모두 끝난 것이다.

 

AWS로 가서 인바운드 설정에 80번 포트를 열어준다.

(아마 기본으로 열려있는 것으로 기억한다.)

 

모두 완료가 되었으니 이제 접속해보자.

 

여기서 주의해야 할 점은 https가 아닌 http로 접속해야 한다는 것이다.

나도 처음에 https로 접속했다가 페이지가 계속 뜨지 않아 계속 찾아봤는데, 생각해보니 기본적으로 url에 접속하면 https로 접속하기에 페이지가 보이지 않았던 것이었다.

 

접속을 하면 이렇게 nginx 기본 페이지가 보이는 것을 확인 할 수 있다.

이제 다음에는 이 nginx에 우리가 만든 페이지를 띄우기로 할 것이다.

'블로그 개발 프로젝트' 카테고리의 다른 글

Redis ERR value is not an integer or out of range  (0) 2023.08.09
Nginx에 페이지 연결하기  (0) 2023.08.07
ExceptionHandler  (0) 2023.07.28
Swagger @ApiModelProperty에 example List  (0) 2023.07.28
Swagger Response  (0) 2023.07.28
반응형

이번에 현장실습 과제가 Docker를 사용해서 지금까지 만든 프로젝트를 배포하는 것이다.

Docker를 공부해야지 해야지 하다가 결국 쓰게 되었다.

과정 자체는 크게 다를 거 같지 않으니 기록을 하며 해보려고 한다.

 

Docker 설치

당연히 Docker를 설치 해야한다.

 

우선 apt를 업데이트 해준다.

sudo apt-get update

 

그 다음엔 앞으로 사용할 프로그램들을 설치해준다.

sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    gnupg \
    lsb-release

 

그리고는 Docker GPG 키를 가져온다.

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

Docker Repository를 추가하고

 echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

 

이제 여기에 Docker를 설치한다

다시 apt-get을 업데이트하고 Docker를 설치해준다.

sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

 

이렇게 버전을 봤을 때 잘 출력이 된다면 정상이다.

 

자바 설치

docker 다운받으면서 혹시나 하고 Java를 봤는데 설치가 안되어있다.

 

java -version만 입력을 해도 

버전들이 밑에 쭉 나온다

프로젝트에 맞게 11로 다운을 받겠다.

 

설치는 쉽다.

환경변수 설정이 어려울 뿐

 

우선 javac의 경로를 가져온다.

readlink로 실제 경로를 찾는다

sudo vi /etc/profile

profile을 열어주고

얻은 정보들을 이용하여 환경변수를 작성해준다.

 

profile을 적용시켜주고 환경변수를 출력해보면 정상작동한다.

 

MariaDB 이미지 생성

스프링 부트를 같은 도커의 MariaDB에 연결 할 것이기 때문에 MariaDB 이미지를 가져온다.

 

docker pull mariadb

이렇게만 하면 mariadb 이미지가 가져와진다.

 

이거로 컨테이너를 생성한다.

docker container run -d -p 3306:3306 -e MARIADB_ROOT_PASSWORD=<비밀번호> --name mariadb-container mariadb

 

docker ps 명령어를 입력해보면 잘 작동하는 것을 볼 수 있다.

 

여기에 들어가서 스키마와 유저를 생성해야 한다.

 

docker exec -it mariadb-container /bin/bash

이 명령어로 mariadb에 들어가서 스키마와 유저를 생성하고 오자

 

Springboot

우선 만든 프로젝트를 pull해서 가져온다.

 

대부분의 경우 application.properties가 비어있을 것이다.

거기에 이렇게 적어주면 된다.

 

여기서 또 생각해보니 maven 설치를 안했다....

maven은

apt install maven

이거로 설치할 수 있다.

 

설치가 완료되었다면 스프링 빌드 프로젝트에서

mvn clean package -DskipTests

로 jar 파일을 만들어준다.

 

target 폴더를 보면 원하는 jar 파일이 생성 된 것을 볼 수 있다.

이제 이것도 도커 위에 올리자

 

우선 Dockerfile로 이미지를 생성해준다.

 

Dockerfile은 이렇게 작성하였다.

FROM openjdk:11
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} demo-0.0.1-SNAPSHOT.jar
ENTRYPOINT ["java","-jar", "demo-0.0.1-SNAPSHOT.jar"]

 

해당 위치에서 

sudo docker build -t spring-boot-image .

이 명령어로 이미지를 생성해준다.

 

그리고 해당 이미지로 컨테이너를 만들기 위해 

version: '3'
services:
        mariadb:
                image: mariadb
                container_name: mariadb-container
                environment:
                        MARIADB_ROOT_PASSWORD: password
                ports:
                        - "????:????"
        spring-boot-app:
                image: spring-boot-image
                container_name: spring-boot-container
                restart: always
                depends_on:
                        - mariadb
                ports:
                        - "????:????"
                environment:
                        SPRING_DATASOURCE_URL: jdbc:mariadb://mariadb:3306/knudb
                        SPRING_DATASOURCE_USERNAME: user
                        SPRING_DATASOURCE_PASSWORD: password

이렇게 docker-compose.yml을 작성하고

sudo docker-compose up -d

로 컨테이너를 만들어주면 끝이다!

'현장실습' 카테고리의 다른 글

Duplicate entry error  (0) 2023.07.20
반응형

당연히 예외에 대해 처리를 할 수 있도록 만들어야 한다.

이런 부분들은 보통 Service단에 try-catch로 정의를 했지만, 항상 보며 느끼는 부분은 코드가 지저분해지고 가독성이 많이 떨어진다.

 

어떻게 할지 고민하던 중, 실습 기간 대리님께서 RestControllerAdvice를 써보라고 하셔서 이번 프로젝트에는 이것을 이용해 예외 처리를 최대한 깔끔하게 해보려고 한다.

 

그냥 사용하려면 방법은 어렵지 않다.

@RestControllerAdvice

그냥 이런 annotation을 가지고 있는 class를 정의해준다.

 

그리고 안에

@ExceptionHandler(Exception.class)
    public ResponseEntity<String> GlobalHandler() {
        return new ResponseEntity<>("에러, 자세한 상황을 보고해주세요", HttpStatus.BAD_REQUEST);
    }

이렇게 어떤 에러를 어떻게 처리할 것인지 작성을 해준다.

 

물론 이렇게 작성해버리면 모든 에러가 여기에서 잡혀버리기 때문에 Exception.class는 안 쓸 거 같다.

 

근데 이걸 사용하려니 한 가지 걸리는 부분이 있었다.

만약 SQLException이 발생했다고 하자, 하지만 이 에러가 어느 컨트롤러에서 발생했는 지에 따라 다른 오류일텐데 Global로 처리를 해버리면 똑같이 처리 과정을 거치게 된다.

 

그래서 나는 각 Controller에 다른 handler를 사용하기 위해 

@RestControllerAdvice(basePackageClasses = ???.class)

이렇게 어떤 컨드롤러인지 basePackageClasses에 명시해서 해당 컨트롤러만 사용하도록 만들었다.

 

그러면 또 여기서 문제가 생긴다.

공통적으로 발생하는 예외들은 다 복붙을 해서 적어야 하나...

솔직히 얼마 안 걸리지만, 중복되는 코드들을 계속 적는 부분이 마음에 걸렸다.

 

그렇기에 Global 부분에

package cobo.blog.global.Config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.sql.SQLException;
import java.sql.SQLTimeoutException;

@Slf4j
public class GlobalExceptionHandler {

    /**
     * DB 관련 에러
     */

    @ExceptionHandler(SQLTimeoutException.class)
    public ResponseEntity<String> GlobalSQLTimeoutHandler(){
        log.info("GlobalSQLTimeoutHandler: {}", this.getClass());
        return new ResponseEntity<>("데이터베이스 접근 시간 초과", HttpStatus.REQUEST_TIMEOUT);
    }

    @ExceptionHandler(SQLException.class)
    public ResponseEntity<String> GlobalSQLHandler(){
        log.info("GlobalSQLHandler: {}", this.getClass());
        return new ResponseEntity<>("데이터 베이스 관련 에러, 자세한 상황을 보고해주세요", HttpStatus.BAD_REQUEST);
    }
}

이런 식으로 공통부분은 구현을 하며, 각 세부 컨트롤러에서 상속을 받아 더 세부적으로 작업할 수 있도록 하였다.

오랫동안 생각해 본 결과 이 방법이 지금 내가 할 수 있는 최선의 방법이라고 생각했고, 후에는 아마 다른 방법도 사용하지 않을까?

'블로그 개발 프로젝트' 카테고리의 다른 글

Nginx에 페이지 연결하기  (0) 2023.08.07
EC2에 Nginx 초기 설정  (0) 2023.08.05
Swagger @ApiModelProperty에 example List  (0) 2023.07.28
Swagger Response  (0) 2023.07.28
CORS Error  (0) 2023.07.28
반응형

프론트 팀에서 Cors와 함께 온 연락으로 Swagger Example의 수정 요청이었다.

 

Swagger의 API를 누르면 밑에 응답의 예시로 Example Value가 보여지게 되는 데, 응답의 예시가 잘못 나온 다는 것이었다.

나는 당연히 '내가 잘못 쳤구나'라는 생각으로 코드를 봤는데 코드에 이상은 없었다.

하지만 에러는 발생하였다.

 

코드와 같이 좀 더 자세히 보도록 하자

현재 Response가 TechPostRes.class로 작성이 되어있다.

해당하는 TechPostRes이다.

package cobo.blog.domain.Home.Data.Dto;

import cobo.blog.global.Data.Entity.TechPostEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
public class TechPostRes {

    @ApiModelProperty(
            value = "TechPost 제목",
            example = "한승규의 스프링 고민"
    )
    private String title;
    @ApiModelProperty(
            value = "작성한 사람의 이름",
            example = "한승규"
    )
    private String user;

    @ApiModelProperty(
            value = "작성한 날짜를 yyyyMMdd 8자리 문자열로 응답",
            example = "20230711"
    )
    private String Date;

    public TechPostRes(TechPostEntity techPost) {
        this.title = techPost.getTitle();
        this.user = techPost.getUser().getName();
        this.Date = techPost.getCreatedAt().toString();
    }
}

하지만...? 이에 해당하는 Example Value를 보자.

DTO와 다르게 나온다.

분명히 3개의 멤버에 대한 값이 나와야 하는데, 지금 엄청 많이 나오고 있다.

 

왜 그럴지 생각을 해보았는데

지금 TechPostRes라는 클래스가 다른 패키지에 작성이 되어 있다.

다른 페이지에서 사용하는 API에 대한 Response이다.

 

확인을 해보니 그 DTO에 대한 Example Value였다.

그럼 내가 그 DTO로 잘못 import 한 것일까?

확인을 해보면 다른 패키지에서 가져오는 TechPostRes는 없는 것을 볼 수 있다.

 

뭔가 Swagger가 이름 같으면 그냥 가져오는 것 같아서 앞에 어떤 Controller가 사용하는지로 이름을 변경하였다.

 

TechPostRes앞에 Home을 붙여 HomeTechPostRes라고 작성하였는데 

이렇게 하니까

제대로 Example Value가 나오는 것을 볼 수 있다.

 

결론 Home패키지 안에 있어서 controller의 이름을 붙이지 않으려 했는데, 어쩔 수 없이 이름들을 구분하기 위해 붙여주게 되었다.

DTO를 작성하더라도 이름을 모두 다르게 하자

'블로그 개발 프로젝트' 카테고리의 다른 글

ExceptionHandler  (0) 2023.07.28
Swagger @ApiModelProperty에 example List  (0) 2023.07.28
CORS Error  (0) 2023.07.28
JPA으로 Paging  (0) 2023.07.24
JPA TopBy  (0) 2023.07.24
반응형

프론트 팀에서 CORS에러가 발생했다고 연락이 왔다.

항상 그냥 만들다가 나중에 추가하는 거 같은....

프로젝트를 할 때마다 항상 발생하는 CORS 에러이다...

이 CORS에러가 왜 발생하는지는 이 글을 읽는 사람들이라면 알고 있을 것이다.

간단하게 출처가 같지 않아서 발생하는 에러이다.

 

이전에는 CORS 에러가 발생할 때마다 항상 귀찮아서 컨트롤러 상단에

@CrossOrigin("*")

이렇게 Annotation을 추가했었다.

 

이 방법은 그냥 빠르게 임시 방편으로 사용했던 것 같고, 이번에는 이 에러를 초기에 한 번에 잡고 후에 NginX를 사용하면 이 구성을 뺄 수도 있기 때문에 Config를 만들어서 빼두었다.

 

방법은 어렵지 않았다.

우선 Configuration을 만들어두는 패키지에 당연히 이름은 상관없는 Configuration을 하나 만들고 WebMvcConfiguer를 implements한다.

 

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .maxAge(6000);
    }
}

 

그리고 이렇게 addCorsMappings를 Overide한다.

 

addMapping은 어느 URL에 적용을 할 것인가?

allowedOrigins는 어느 ip의 요청에 적용을 할 것인가?

allowedMethods는 어느 메소드를 허용 할 것인가?(Ex GET, POST)

maxAge는 캐싱의 시간이다.

 

어차피 현재 작성한 CorsMapping은 개발 단계에서만 사용 할 것이기 때문에 모두 와일드카드로 작성했다.

아마 후에 NginX로 설정을 옮길 것 같고 그 때가 된다면 다시 어떻게 적용했는지 적도록 하겠다.

'블로그 개발 프로젝트' 카테고리의 다른 글

Swagger @ApiModelProperty에 example List  (0) 2023.07.28
Swagger Response  (0) 2023.07.28
JPA으로 Paging  (0) 2023.07.24
JPA TopBy  (0) 2023.07.24
@ManyToOne, @OneToMany  (0) 2023.07.24

+ Recent posts