Middleware/Kafka&RabbitMQ 2020. 7. 26. 17:32

 

 

yoonyeoseong/rabbitmq-sample

Contribute to yoonyeoseong/rabbitmq-sample development by creating an account on GitHub.

github.com

 

오늘 포스팅할 내용은 래빗엠큐이다. 그 동안에는 카프카를 사용할 일이 많아 카프카에 대한 포스팅이 주였는데, 이번에 래빗엠큐를 사용할 일이 생겨 간단히 래빗엠큐에 대해 간단히 다루어 볼것이다.(예제 코드는 위 깃헙에 올려놓았습니다.)

 

비동기 작업에 있어 큐를 사용하려면 중간에 메시지 브로커라는 개념이 존재하는데, 이러한 메시지 브로커에는 RabbitMQ, Kafka 등이 대표적으로 있다. 해당 포스트에서는 표준 MQ프로토콜인 AMQP를 구현한 RabbitMQ(래빗엠큐)에 대해 다루어볼 것이다.

 

간단하게 메시지큐는 아래 그림과 같은 워크 플로우로 이루어져있다. 

 

 

대부분의 메시지큐는 프로듀서가 있고, 해당 프로듀서가 브로커로 메시지를 발행하면, 적절한 컨슈머가 해당 메시지를 구독(읽다)하는 구조이다. 그렇다면 래빗엠큐는 상세하게 어떠한 구조로 되어있을까?

 

래빗엠큐는 단순히 프로듀서가 브로커로 메시지를 전달하여 컨슈머가 큐를 읽어가는 구조라는 면에서는 동일하지만, 프로듀싱하는 과정에서 조금 더 복잡한 개념이 들어간다. 바로 exchange와 route, queue라는 개념이다.

 

간단하게 워크 플로우를 설명하자면 아래와 같다.

 

  1. Producer는 Message를 Exchange에게 보내게 됩니다.
    1. Exchange를 생성할때 Exchange의 Type을 정해야 합니다.
  2. Exchange는 Routing Key를 사용하여 적절한 Queue로 Routing을 진행합니다.
    1. Routing은 Exchange Type에 따라 전략이 바뀌게 됩니다.
  3. Exchange - Queue와 Binding이 완료된 모습을 볼 수 있습니다.
    1. Message 속성에 따라 적절한 Queue로 Routing이 됩니다.
  4. Message는 Consumer가 소비할때까지 Queue에 대기하게 됩니다.
  5. Consumer는 Message를 소비하게 됩니다.

 

위에서 1번에 Exchange라는 개념이 등장하는데, Exchange는 정해진 규칙으로 메시지를 라우팅하는 기능을 가지고 있다. 여기서 정해진 규칙은 크게 4가지가 존재하는데, 규칙들은 아래와 같다.

 

exchange routeing 전략

  • Direct Exchange
    • Message의 Routing Key와 정확히 일치하는 Binding된 Queue로 Routing
  • Fanout Exchange
    • Binding된 모든 Queue에 Message를 Routing
  • Topic Exchange
    • 특정 Routing Pattern이 일치하는 Queue로 Routing
  • Headers Exchange
    • key-value로 정의된 Header 속성을 통한 Routing

 

 

 

익스체인지는 위와 같은 전략으로 메시지를 라우팅하게 된다. 그리고 자주 사용되는 Exchange의 옵션으로는 아래와 같이 있다.

 

exchange options
  • Durability
    • 브로커가 재시작 될 때 남아 있는지 여부
    • durable -> 재시작해도 유지가능
    • transient -> 재시작하면 사라집니다.
  • Auto-delete
    • 마지막 Queue 연결이 해제되면 삭제

마지막으로 래빗엠큐에서 사용되는 용어가 있다.

 

  • Vhost(virutal host)
    • Virtual Host를 통해서 하나의 RabbitMQ 인스턴스 안에 사용하고 있는 Application을 분리할 수 있습니다.
  • Connection
    • 물리적인 TCP Connection, HTTPS -> TLS(SSL) Connection을 사용
  • Channel
    • 하나의 물리적인 Connection 내에 생성되는 가상의 Connection
    • Consumer의 process나 thread는 각자 Channel을 통해 Queue에 연결 될 수 있습니다.

 

여기까지 간단하게 래빗엠큐의 동작과 용어에 대해서 다루어봤으니 실제 코드 레벨로 실습을 진행해본다.

 

docker rabbitmq 설치
sudo docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 \
	--restart=unless-stopped -e RABBITMQ_DEFAULT_USER=username \
    -e RABBITMQ_DEFAULT_PASS=password rabbitmq:management

 

위처럼 도커로 래빗엠큐를 설치해주고, http://localhost:15672(username/password)로 접속해보자.

 

springboot rabbitmq 예제

build.gradle

plugins {
    id 'org.springframework.boot' version '2.3.2.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'com.levi'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '14'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-amqp'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.springframework.amqp:spring-rabbit-test'
}

test {
    useJUnitPlatform()
}

 

application.yml

rabbitmq:
  test:
    username: username
    password: password
    host: localhost
    port: 5672
    virtualHost: levi.vhost
    routeKey: test.route
    exchangeName: test.exchange
    queueName: testQueue
    deadLetterExchange: dead.letter.exchange
    deadLetterRouteKey: dead.letter.route

 

application.yml에서 프로퍼티를 읽어오는 클래스를 아래 하나 만들었다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
@ConfigurationProperties(prefix = "rabbitmq")
@Getter
@Setter
public class RabbitMqProperty {
    private RabbitMqDetailProperty test;
 
    @Getter
    @Setter
    public static class RabbitMqDetailProperty {
        private String username;
        private String password;
        private String host;
        private int port;
        private String virtualHost;
        private String routeKey;
        private String exchangeName;
        private String queueName;
        private String deadLetterExchange;
        private String deadLetterRouteKey;
    }
}
cs

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@SpringBootApplication(exclude = {
        RabbitAutoConfiguration.class
})
@EnableRabbit
@RestController
@RequiredArgsConstructor
public class RabbitmqApplication {
    private final RabbitTemplate testTemplate;
 
    public static void main(String[] args) {
        SpringApplication.run(RabbitmqApplication.class, args);
    }
 
    @GetMapping("/")
    public String inQueue() {
        testTemplate.convertAndSend(SampleMessage.of("message!!"));
        return "success";
    }
 
    @Data
    @AllArgsConstructor(staticName = "of")
    @NoArgsConstructor
    public static class SampleMessage {
        private String message;
    }
}
cs

 

래빗엠큐 auto configuration을 꺼주었고, 메시지를 인큐하기 위한 컨트롤러를 하나 만들어 주었다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@Slf4j
@Configuration
@RequiredArgsConstructor
public class RabbitMqConfig {
    private final RabbitMqProperty rabbitMqProperty;
 
    @Bean
    public ConnectionFactory testConnectionFactory() {
        final RabbitMqProperty.RabbitMqDetailProperty test = rabbitMqProperty.getTest();
        final CachingConnectionFactory factory = new CachingConnectionFactory();
        factory.setHost(test.getHost());
        factory.setPort(test.getPort());
        factory.setUsername(test.getUsername());
        factory.setPassword(test.getPassword());
        factory.setVirtualHost(test.getVirtualHost());
 
        return factory;
    }
 
    @Bean
    public SimpleRabbitListenerContainerFactory testContainer(final ConnectionFactory testConnectionFactory) {
        final SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(testConnectionFactory);
        factory.setConnectionFactory(testConnectionFactory);
        factory.setErrorHandler(t -> log.error("ErrorHandler = {}", t.getMessage()));
        factory.setDefaultRequeueRejected(false); //true일 경우 리스너에서 예외가 발생시 다시 큐에 메시지가 쌓이게 된다.(예외상황 해결안될 시 무한루프)
        factory.setMessageConverter(jacksonConverter());
        //예외 발생시 recover할 수 있는 옵션, 위 에러 핸들러와 잘 구분해서 사용해야할듯
//위 핸들러와 적용 순서등도 테스트가 필요(혹은 둘다 동시에 적용되나?)
//        factory.setAdviceChain(
//                RetryInterceptorBuilder
//                .stateless()
//                .maxAttempts(3) //최대 재시도 횟수
//                .recoverer() //recover handler
//                .backOffOptions(2000, 4, 10000) //2초 간격으로, 4번, 최대 10초 내에 재시도
//                .build()
//        );
        return factory;
    }
 
    @Bean
    public RabbitTemplate testTemplate(final ConnectionFactory testConnectionFactory, final MessageConverter jacksonConverter) {
        final RabbitMqProperty.RabbitMqDetailProperty property = rabbitMqProperty.getTest();
        final RabbitTemplate template = new RabbitTemplate();
        template.setConnectionFactory(testConnectionFactory);
        template.setMessageConverter(jacksonConverter);
        template.setExchange(property.getExchangeName());
        template.setRoutingKey(property.getRouteKey());
        return template;
    }
 
    @Bean
    public MessageConverter jacksonConverter() {
        return new Jackson2JsonMessageConverter();
    }

//app에서 큐만들거나 바인딩 하기 위해서는 RabbitAdmin 필요
    @Bean
    public RabbitAdmin testAdmin(final ConnectionFactory testConnectionFactory) {
        return new RabbitAdmin(testConnectionFactory);
    }
}
cs

 

브로커 연결 및 메시지를 프로듀싱하기 위한 RabbitTemplate 설정이다. 이중 RabbitAdmin 빈을 만들고 있는 수동이 아니라, 앱에서 큐를 만들고 바인딩 하기 위해서는 RabbitAdmin이 빈으로 떠있어야한다. 기타 설정은 주석을 참고하자 !

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Configuration
@RequiredArgsConstructor
public class RabbitMqBindingConfig {
    private final RabbitMqProperty rabbitMqProperty;
 
    @Bean
    public TopicExchange testTopicExchange() {
        return ExchangeBuilder
                .topicExchange(rabbitMqProperty.getTest().getExchangeName())
                .durable(true)
                .build();
    }
 
    @Bean
    public TopicExchange dlExchange() {
        return ExchangeBuilder
                .topicExchange(rabbitMqProperty.getTest().getDeadLetterExchange())
                .durable(true)
                .build();
    }
 
    @Bean
    public Queue testQueue() {
        final RabbitMqProperty.RabbitMqDetailProperty testDetail = rabbitMqProperty.getTest();
        return QueueBuilder
                .durable(testDetail.getQueueName())
                .deadLetterExchange(testDetail.getDeadLetterExchange())
                .deadLetterRoutingKey(testDetail.getDeadLetterRouteKey())
                .build();
    }
 
    @Bean
    public Queue dlq() {
        return QueueBuilder
                .durable("DEAD_LETTER_QUEUE")
                .build();
    }
 
    @Bean
    public Binding testBinding(final Queue testQueue, final TopicExchange testTopicExchange) {
        return BindingBuilder
                .bind(testQueue)
                .to(testTopicExchange)
                .with(rabbitMqProperty.getTest().getRouteKey());
    }
 
    @Bean
    public Binding dlBinding(final Queue dlq, final TopicExchange dlExchange) {
        return BindingBuilder
                .bind(dlq)
                .to(dlExchange)
                .with(rabbitMqProperty.getTest().getDeadLetterRouteKey());
    }
}
cs

 

 

프로듀싱과 컨슈밍을 위해 브로커에 exchange와 queue를 binding하는 설정이다. 하나 추가적으로는 컨슈밍에서 예외가 발생하였을 때, Recover를 위한 Dead Letter Queue를 선언하였다.

 

이제 앱을 실행시키고, localhost:8080으로 요청을 보내보자 ! 요청을 보내면 메시지를 프로듀싱 할것이고, 해당 메시지를 컨슈밍해서 로그를 찍게 될 것이다. 여기까지 간단하게 래빗엠큐에 대해 다루어봤다.

 

 

 

https://jonnung.dev/rabbitmq/2019/02/06/about-amqp-implementtation-of-rabbitmq/

 

조은우 개발 블로그

 

jonnung.dev

https://velog.io/@hellozin/Spring-Boot%EC%99%80-RabbitMQ-%EC%B4%88%EA%B0%84%EB%8B%A8-%EC%84%A4%EB%AA%85%EC%84%9C

 

Spring Boot와 RabbitMQ 초간단 설명서

이번 포스트에서는 Spring boot 프로젝트에서 RabbitMQ를 사용하는 간단한 방법을 알아보겠습니다. Consumer 코드와 Producer 코드는 GitHub에 있습니다. 먼저 RabbitMQ 서버를 실행해야 하는데 Docker를 사용하�

velog.io

https://nesoy.github.io/articles/2019-02/RabbitMQ

 

RabbitMQ에 대해

 

nesoy.github.io

 

posted by 여성게
:
Web/Netty 2020. 2. 1. 14:12

 

오늘 다루어볼 포스팅 내용은 Netty의 개념과 아키텍쳐에 대한 대략적인 설명이다. Netty에 대해 알아보기 전에 AS-IS 자바의 네트워킹 동작 방식에 대해 먼저 다루어본다.

 

자바의 네트워킹

순수 자바로 네트워크 통신을 하기위해서 생긴 최초의 라이브러리는 java.net 패키지이다. 해당 소켓 라이브러리가 제공하는 방식은 블로킹 함수만 지원했다. 해당 라이브러리를 이용한 서버코드를 간단히 보면 아래와 같다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public void blockCall() throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        Socket clientSocket = serverSocket.accept();
        BufferedReader in = new BufferedReader(
                new InputStreamReader(clientSocket.getInputStream())
        );
 
        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
 
        String request, response;
        while ((request = in.readLine()) != null) {
            if ("OK".equals(request)) {
                break;
            }
            response = processRequest(request);
            System.out.println(response);
        }
    }
 
    public String processRequest(String request) {
        return request + "Done";
    }
cs

 

해당 코드는 한 번에 한 연결만 처리한다. 다수의 동시 클라이언트를 관리하려면 새로운 클라이언트 Socket마다 새로운 Thread를 할당해야한다. 이런식의 블로킹 처리는 어떠한 결과를 초래하게 될 것인가? 여러 스레드가 입력이나 출력 데이터가 들어오기를 기다리며 무한정 대기 상태로 유지될 수 있고, 이것은 고로 리소스의 낭비로 이어진다. 그리고 하나의 연결당 하나의 스레드가 생성되므로, 많은 수의 클라이언트를 관리하기 위해서는 많은 수의 스레드를 생성해야 하고, 이것은 리소스 낭비는 물론 잦은 컨텍스트 스위칭에 의한 오버헤드가 발생하게 된다. 

 

이러한 문제때문에 그 다음 나온 자바의 네트워킹 통신은 자바 NIO방식이다.

 

자바 NIO

해당 방식은 네트워크 리소스 사용률을 세부적으로 제어할 수 있는 논블로킹 호출이 포함되어 있다. 논블로킹은 내부적으로 시스템의 이벤트 통지 API를 이용해 논블로킹 소켓의 집합을 등록하면 읽거나 기록할 데이터가 준비됐는지 여부를 알 수 있다. 즉, 계속해서 기다릴 필요가 없어지는 것이다.

 

 

OIO와는 달리 NIO는 채널과 소켓이 1:1 매칭되지 않는다. java.nio.channels.Selector가 논블로킹 Socket의 집합에서 입출력이 가능한 항목을 지정하기 위해 이벤트 통지 API를 이용하기 때문에, 언제든지 읽기나 쓰기 작업의 완료상태를 확인가능하다. 그렇기 때문에 채널당 하나의 쓰레드가 분배되지 않고(블록킹하면 기다리지 않아도) 필요할때마다 쓰레드에게 통지를 하므로써 필요할때만 일을 할 수 있고, 필요하지 않을때는 다른 일을 할 수 있게 한다. 이런식의 처리흐름을 도입함으로써

 

  • 적은 수의 스레드로 더 많은 연결을 처리할 수 있어 메모리 관리와 컨텍스트 스위치에 대한 오버헤드가 준다.
  • 입출력을 처리하지 않을 때는 스레드를 다른 작업에 활용할 수 있다.

 

라는 장점이 생긴다. 하지만 그에 따른 단점이 존재한다면, 순수 자바 라이브러리를 직접 사용해 애플리케이션을 제작하기에는 아주 어렵다. 특히 부하가 높은 상황에서 입출력을 안정적이고 효율적으로 처리하고 호출하는 것과 같이 까다롭고 문제 발생이 높은 일은 어려운 일이기 때문이다.

 

이러한 어렵고 까다로운 일을 대신해주기 위해 네티와 같은 네트워크 프레임워크가 존재하는 것이다.

 

Netty(네티)

네티는 위와 같이 어려운 자바의 고급 API를 내부에 숨겨 놓고, 사용자에게 비즈니스 로직에만 집중할 수 있도록 추상화한 API를 제공한다. 또한 적은양의 리소스를 소모하면서 더 많은 요청을 처리할 수 있게 설계되었으므로 확장하기에도 부담이 없다. 아래는 네티의 특징을 요약한 표이다.

 

category feature
설계 단일 API로 블로킹과 논블로킹 방식의 여러 전송 유형을 지원하며, 단순하지만 강력한 스레딩 모델을 제공한다. 
이용 편의성 JDK 1/6+을 제외한 추가 의존성이 필요 없다.
성능 코어 자바 API보다 높은 처리량과 짧은 지연시간을 갖는다. 풀링과 재사용을 통한 리소스 소비를 감소시켰고, 메모리 복사를 최소화하였다.
견고성 저속, 고속 또는 과부하 연결로 인한 OOM이 잘 발생하지 않는다.(요청을 처리하는 스레드 수가 굉장히 적기 때문) 고속 네트워크 상의 NIO 애플리케이션에서 일반적인 읽기/쓰기 비율 불균형이 발생하지 않는다.
보안 완벽한 SSL/TLS 및 StarTLS 지원. 애플릿이나 OSGi 같은 제한된 환경에서도 이용 가능.

 

비동기식 이벤트 기반 네트워킹

 

  • 네티의 논블로킹 네트워크 연결은 작업 완료를 기다릴 필요가 없다. 완전 비동기 입출력은 이 특징을 바탕으로 한 단계 더 나아간다. 비동기 메서드는 즉시 반환하며 작업이 완료되면 직접 또는 나중에 이를 통지한다. 
  • 셀렉터는 적은 수의 스레드로 여러 연결에서 이벤트를 모니터링할 수 있게 해준다

 

위의 특징들을 종합해보면 블로킹 입출력 방식을 이용할 때보다 더 많은 이벤트를 훨씬 빠르고 경제적으로 처리할 수 있다. 

 

네티의 핵심 컴포넌트들

  • Channel
  • Callback
  • Future
  • 이벤트와 핸들러

Channel은 하나 이상의 입출력 작업을 수행할 수 있는 하드웨어 장치, 파일, 네트워크 소켓, 프로그램 컴포넌트와 같은 엔티티에 대한 열린 연결을 뜻한다. 쉽게 말해 인바운드 데이터와 아웃바운드 데이터를 위한 운송수단이라고 생각하면 좋을 것 같다.

 

Callback은 간단히 말해 다른 메서드로 자신에 대한 참조를 제공할 수 있는 메서드다. 관심 대상에게 작업 완료를 알리는 가장 일반적인 방법 중 하나이다. 네티는 이러한 콜백을 내부적으로 이벤트 처리에 사용한다.

 

Future는 작업이 완료되면 애플리케이션에게 알리는 한 방법이다. 즉, 작업이 완료되는 미래의 어떤 시점에 그 결과에 접근할 수 있게 해준다. 하지만 자바의 Future는 결과를 얻기 위해서는 반드시 블로킹해야만 한다. 그래서 네티는 비동기 작업이 실행됐을 때 이용할 수 있는 자체 구현 ChannelFuture를 제공한다. 더 자세한 내용은 뒤에서 다루어본다.

 

마지막으로 네티는 이벤트가 들어오면 해당 이벤트를 핸들러 클래스의 사용자 구현 메서드로 전달하여 이벤트를 변환하며 이러한 핸들러의 체인으로 이벤트들을 처리한다. 이벤트와 핸들러 또한 뒤에서 더 자세히 다룬다.

posted by 여성게
:
Middleware/Kafka&RabbitMQ 2019. 2. 18. 00:39

Spring - Rabbitmq를 이용한 비동기 메시징 서비스

     -리액티브 마이크로서비스



RabbitMQ에 대해

Mac OS 환경에서 작성되었습니다.


오늘은 간단히 Spring boot + Rabbitmq를 이용한 비동기 메시징 서비스를 구현해볼 것이다. 

일단 이 포스팅을 진행하는 이유는 요즘 시대에는 일체형 애플리케이션이 작은 서비스 단위인 마이크로서비스 단위로 나누어 

서비스 단위로 배포하는 아키텍쳐가 대세인듯하다. 

이 말은 즉슨, 아주 큰 애플리케이션이 작은 서비스 단위(마이크로서비스)로 나뉘어 각각 단독적으로 독립적으로 실행가능한 상태로 배포가 된다. 

이런 경우 마이크로서비스끼리의 통신은 RESTful한 통신도 있지만 메시지 큐와 같은 서비스를 이용하여 비동기적으로 통신하기도 한다.

그리고 이 구조를 발행구독구조라고 한다.


위의 사진과 같이 두 마이크로서비스 애플리케이션이 외부의 큐로 연결되는 구조인 것이다.

간단히 설명하면 Sender라는 하나의 애플리케이션에서 큐로 발신하면 

Receiver라는 애플리케이션이 해당 큐에 대한 이벤트를 수신하여

로직을 처리하게 되는 것이다.





외부의 큐도 어찌됬는 하나의 데몬으로 떠있는 애플리케이션 같은 것이다.

Rabbitmq를 다운로드한다.


▶︎▶︎▶︎래빗엠큐다운로드



Standalone MacOS binary를 클릭하여 받는다.




Rabbitmq 관리자 페이지 플러그인을 활성화하는 명령이다.



관리자 페이지에 들어갈 계정을 만들어준다.


만약 계정을 만드는 데 밑의 에러가 발생한다면 


Error:

{:undef, [{:crypto, :hash, [:sha256, <<94, 223, 167, 31, 97, 108, 105, 118, 101>>], []}, {:rabbit_password, :hash, 2, [file: 'src/rabbit_password.erl', line: 34]}, {:rabbit_auth_backend_internal, :add_user_sans_validation, 3, [file: 'src/rabbit_auth_backend_internal.erl', line: 252]}, {:rpc, :"-handle_call_call/6-fun-0-", 5, [file: 'rpc.erl', line: 197]}]}


>brew install openssl 로 인스톨하면 된다.



Rabbitmq를 실행시켜 준다. 그리고 localhost:15672로 접속한 후에 방금 전에 만들었던 계정으로

로그인한다.




위의 사진에 Set permisstion 버튼을 클릭하여 해당 계정으로 큐에서 읽거나 쓰는 권한을 부여한다.


여기까지 간단한 Rabbitmq의 설정이 완료되었다. 이것보다 더 많은 설정 요소들이 존재하겠지만,

예제로 간단하게 이정도로만 설정한다.


참고: 관리자페이지 포트 : 15672

애플리케이션(amqp) :5672

클러스터링 : 25672





소스 예제 회원가입 이후에 외부 큐에 가입 메시지를 날리면(Sender) 수신애플리케이션에서 가입 회원의 이메일을 커맨드에 뿌려준다.(Receiver)



<Sender>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package org.rvslab.chapter3;
 
import java.util.Optional;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
 
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import reactor.core.publisher.Mono;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
 
@SpringBootApplication
@EnableSwagger2
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    
    @Bean
    CommandLineRunner init(CustomerRespository customerRepository) {
            return (evt) ->  {
                              customerRepository.save(new Customer("Adam","adam@boot.com"));
                              customerRepository.save(new Customer("John","john@boot.com"));
                              customerRepository.save(new Customer("Smith","smith@boot.com"));
                              customerRepository.save(new Customer("Edgar","edgar@boot.com"));
                              customerRepository.save(new Customer("Martin","martin@boot.com"));
                              customerRepository.save(new Customer("Tom","tom@boot.com"));
                              customerRepository.save(new Customer("Sean","sean@boot.com"));
            };
    }
    
 
}
 
 
@RestController
class CustomerController{
        
        CustomerRegistrar customerRegistrar;
        
        @Autowired
        CustomerController(CustomerRegistrar customerRegistrar){
                this.customerRegistrar = customerRegistrar;
        }
        
        @RequestMapping( path="/register", method = RequestMethod.POST)
        Mono<Customer> register(@RequestBody Customer customer){                
                return customerRegistrar.register(customer);     
        }
}
 
@Component
@Lazy
class CustomerRegistrar {
        
        CustomerRespository customerRespository;
        Sender sender;
        
        @Autowired
        CustomerRegistrar(CustomerRespository customerRespository, Sender sender){
                this.customerRespository = customerRespository;
                this.sender = sender;
        }
        
        
        public Mono<Customer> registerMono(Mono<Customer> monoCustomer){
                monoCustomer.doOnNext(customer -> {
                        if(customerRespository.findByName(customer.getName()).isPresent())
                                System.out.println("Duplicate Customer");
                        else {
                                customerRespository.save(customer);
                                //sender.send(customer.getEmail());             
                        }
                }).subscribe();
                return monoCustomer;
        }
 
        // ideally repository will return a Mono object
        public Mono<Customer> register(Customer customer){
                        if(customerRespository.findByName(customer.getName()).isPresent())
                                System.out.println("Duplicate Customer. No Action required");
                        else {
                                customerRespository.save(customer);
                                //외부 큐에게 메시지전송
                                sender.send(customer.getEmail());       
                                System.out.println("Rabbitmq send :::: "+customer.toString());
                        }
                return Mono.just(customer);
        }
}
 
 
//외부 큐와 연결하기 위한 Sender를 빈으로 등록한다.
//또 빈으로 Queue객체를 등록해준다. 해당 문자열로 Receiver쪽에서도 동일하게 받아야한다.
//RabbitMessagingTemplate으로 외부 큐에게 메시지를 전송한다.
@Component
@Lazy
class Sender {
        
        RabbitMessagingTemplate template;
        
        @Autowired
        Sender(RabbitMessagingTemplate template){
                this.template = template;
        }
 
        @Bean
        Queue queue() {
                return new Queue("CustomerQ"false);
        }
        
        public void send(String message){
                template.convertAndSend("CustomerQ", message);
                System.out.println("Ready to send message but suppressed "+ message);
                 
        }
}
 
//repository does not support Reactive. Ideally this should use reactive repository
@RepositoryRestResource
@Lazy
interface CustomerRespository extends JpaRepository <Customer,Long>{
        Optional<Customer> findByName(@Param("name"String name);
}
 
 
//Entity class
@Entity
class Customer{
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private String name;
        private String email;
        
        public Customer (){}
 
        
        public Customer(String name, String email) {
                super();
                this.name = name;
                this.email = email;
        }
 
 
        public Long getId() {
                return id;
        }
 
        public void setId(Long id) {
                this.id = id;
        }
 
        public String getName() {
                return name;
        }
 
        public void setName(String name) {
                this.name = name;
        }
 
        public String getEmail() {
                return email;
        }
 
        public void setEmail(String email) {
                this.email = email;
        }
 
        @Override
        public String toString() {
                return "Customer [id=" + id + ", name=" + name + ", email=" + email + "]";
        }
        
         
        
}
cs


  1. management.security.enabled=false
  2.  
  3. spring.rabbitmq.host=localhost
  4. spring.rabbitmq.port=5672
  5. spring.rabbitmq.username=yeoseong
  6. spring.rabbitmq.password=yeoseong


<Receiver>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package org.rvslab.chapter3;
 
import java.util.Optional;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
 
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitMessagingTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
import reactor.core.publisher.Mono;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
 
 
@SpringBootApplication
@EnableSwagger2
public class Application {
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
    
    @Bean
    CommandLineRunner init(CustomerRespository customerRepository) {
            return (evt) ->  {
                              customerRepository.save(new Customer("Adam","adam@boot.com"));
                              customerRepository.save(new Customer("John","john@boot.com"));
                              customerRepository.save(new Customer("Smith","smith@boot.com"));
                              customerRepository.save(new Customer("Edgar","edgar@boot.com"));
                              customerRepository.save(new Customer("Martin","martin@boot.com"));
                              customerRepository.save(new Customer("Tom","tom@boot.com"));
                              customerRepository.save(new Customer("Sean","sean@boot.com"));
            };
    }
    
 
}
 
 
@RestController
class CustomerController{
        
        CustomerRegistrar customerRegistrar;
        
        @Autowired
        CustomerController(CustomerRegistrar customerRegistrar){
                this.customerRegistrar = customerRegistrar;
        }
        
        @RequestMapping( path="/register", method = RequestMethod.POST)
        Mono<Customer> register(@RequestBody Customer customer){                
                return customerRegistrar.register(customer);     
        }
}
 
@Component
@Lazy
class CustomerRegistrar {
        
        CustomerRespository customerRespository;
        Sender sender;
        
        @Autowired
        CustomerRegistrar(CustomerRespository customerRespository, Sender sender){
                this.customerRespository = customerRespository;
                this.sender = sender;
        }
        
        
        public Mono<Customer> registerMono(Mono<Customer> monoCustomer){
                monoCustomer.doOnNext(customer -> {
                        if(customerRespository.findByName(customer.getName()).isPresent())
                                System.out.println("Duplicate Customer");
                        else {
                                customerRespository.save(customer);
                                //sender.send(customer.getEmail());             
                        }
                }).subscribe();
                return monoCustomer;
        }
 
        // ideally repository will return a Mono object
        public Mono<Customer> register(Customer customer){
                        if(customerRespository.findByName(customer.getName()).isPresent())
                                System.out.println("Duplicate Customer. No Action required");
                        else {
                                customerRespository.save(customer);
                                //외부 큐에게 메시지전송
                                sender.send(customer.getEmail());       
                                System.out.println("Rabbitmq send :::: "+customer.toString());
                        }
                return Mono.just(customer);
        }
}
 
 
//외부 큐와 연결하기 위한 Sender를 빈으로 등록한다.
//또 빈으로 Queue객체를 등록해준다. 해당 문자열로 Receiver쪽에서도 동일하게 받아야한다.
//RabbitMessagingTemplate으로 외부 큐에게 메시지를 전송한다.
@Component
@Lazy
class Sender {
        
        RabbitMessagingTemplate template;
        
        @Autowired
        Sender(RabbitMessagingTemplate template){
                this.template = template;
        }
 
        @Bean
        Queue queue() {
                return new Queue("CustomerQ"false);
        }
        
        public void send(String message){
                template.convertAndSend("CustomerQ", message);
                System.out.println("Ready to send message but suppressed "+ message);
                 
        }
}
 
//repository does not support Reactive. Ideally this should use reactive repository
@RepositoryRestResource
@Lazy
interface CustomerRespository extends JpaRepository <Customer,Long>{
        Optional<Customer> findByName(@Param("name"String name);
}
 
 
//Entity class
@Entity
class Customer{
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private String name;
        private String email;
        
        public Customer (){}
 
        
        public Customer(String name, String email) {
                super();
                this.name = name;
                this.email = email;
        }
 
 
        public Long getId() {
                return id;
        }
 
        public void setId(Long id) {
                this.id = id;
        }
 
        public String getName() {
                return name;
        }
 
        public void setName(String name) {
                this.name = name;
        }
 
        public String getEmail() {
                return email;
        }
 
        public void setEmail(String email) {
                this.email = email;
        }
 
        @Override
        public String toString() {
                return "Customer [id=" + id + ", name=" + name + ", email=" + email + "]";
        }
        
         
        
}
cs


  1. server.port=8090
  2.  
  3. spring.rabbitmq.host=localhost
  4. spring.rabbitmq.port=5672
  5. spring.rabbitmq.username=yeoseong
  6. spring.rabbitmq.password=yeoseong
  7.  
  8. spring.mail.host=localhost
  9. spring.mail.port=2525


부트 프로젝트생성할 때 의존성 설정 부분에서 I/O>AMQP를 선택해준다.



posted by 여성게
:


ajax를 이용한 실시간 아이디 중복 체크 구현



1. 개발 환경




1. Spring 4.3.7

2. tomcat 8.0

3. oracle





2. 프로젝트 구조






3. UserDTO, UserDAO 구현


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.web.nuri.user;
 
public class UserDTO {
    private String id;
    private String pw;
    private String pwConfirm;
    private String nickName;
    private int grade;
    private int isAdmin;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getPw() {
        return pw;
    }
    public void setPw(String pw) {
        this.pw = pw;
    }
    public String getPwConfirm() {
        return pwConfirm;
    }
    public void setPwConfirm(String pwConfirm) {
        this.pwConfirm = pwConfirm;
    }
    public String getNickName() {
        return nickName;
    }
    public void setNickName(String nickName) {
        this.nickName = nickName;
    }
    public int getGrade() {
        return grade;
    }
    public void setGrade(int grade) {
        this.grade = grade;
    }
    public int getIsAdmin() {
        return isAdmin;
    }
    public void setIsAdmin(int isAdmin) {
        this.isAdmin = isAdmin;
    }
    @Override
    public String toString() {
        return "UserDTO [id=" + id + ", pw=" + pw + ", nickName=" + nickName + ", grade=" + grade + ", isAdmin="
                + isAdmin + "]";
    }
    
}
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.web.nuri.userImpl;
 
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
 
import com.web.nuri.user.UserDTO;
import com.web.nuri.user.UserService;
 
@Repository("UserDAO")
public class UserDAO{
    
    @Autowired
    private SqlSessionTemplate mybatis;
    
    public UserDAO() {
        // TODO Auto-generated constructor stub
        System.out.println("UserDAO 객체 생성");
    }
    public void updateUser(UserDTO udto){
        System.out.println("UserDAO.updateUser() 호출");
        mybatis.update("UserDAO.updateUser",udto);
    }
    public void deleteUser(UserDTO udto){
        System.out.println("UserDAO.deleteUser() 호출");
        mybatis.delete("UserDAO.deleteUser",udto);
    }
    public void insertUser(UserDTO udto) {
        System.out.println("UserDAO.insertUser() 호출");
        mybatis.insert("UserDAO.insertUser",udto);
    }
    public UserDTO getUser(UserDTO udto){
        System.out.println("UserDAO.getUser() 호출");
        return (UserDTO)mybatis.selectOne("UserDAO.getUser",udto);
    }
    public UserDTO getNickNameUser(UserDTO udto) {
        System.out.println("UserDAO.getNickNameUser() 호출");
        return (UserDTO)mybatis.selectOne("UserDAO.getNickNameUser",udto);
    }
    /*public int confirmUser(UserDTO udto) {
        System.out.println("UserDAO.confirmUser() 호출");
        return mybatis.selectOne("UserDAO.confirmUser",udto);
    }*/
}
 
cs





4. UserService, UserServiceImpl 구현



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.web.nuri.user;
 
public interface UserService {
 
    void updateUser(UserDTO udto);
 
    void deleteUser(UserDTO udto);
 
    void insertUser(UserDTO udto);
 
    UserDTO getUser(UserDTO udto);
    
    UserDTO getNickNameUser(UserDTO udto);
    //int confirmUser(UserDTO udto);
 
}
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.web.nuri.userImpl;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
 
import com.web.nuri.user.UserDTO;
import com.web.nuri.user.UserService;
 
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    private UserDAO udao;
    public UserServiceImpl() {
        // TODO Auto-generated constructor stub
        System.out.println("UserServiceImpl() 객체 생성");
    }
    @Override
    public void updateUser(UserDTO udto) {
        // TODO Auto-generated method stub
        System.out.println("UserServiceImpl:updateUser() 호출");
        udao.updateUser(udto);
    }
 
    @Override
    public void deleteUser(UserDTO udto) {
        // TODO Auto-generated method stub
        System.out.println("UserServiceImpl:deleteUser() 호출");
        udao.deleteUser(udto);
    }
 
    @Override
    public void insertUser(UserDTO udto) {
        // TODO Auto-generated method stub
        System.out.println("UserServiceImpl:insertUser() 호출");
        udao.insertUser(udto);
    }
 
    @Override
    public UserDTO getUser(UserDTO udto) {
        // TODO Auto-generated method stub
        System.out.println("UserServiceImpl:getUser() 호출");
        return udao.getUser(udto);
    }
    @Override
    public UserDTO getNickNameUser(UserDTO udto) {
        // TODO Auto-generated method stub
        return udao.getNickNameUser(udto);
    }
 
    /*@Override
    public int confirmUser(UserDTO udto) {
        // TODO Auto-generated method stub
        System.out.println("UserServiceImpl:confirmUser() 호출");
        return udao.confirmUser(udto);
    }*/
 
}
 
cs


UserService클래스와 그 구현 클래스인 UserServiceImpl를 구현한 이유는 컨트롤러에서 직접적으로 DAO에 접근을 하지 않고 Service 클래스를 한번 거쳐 비즈니스 로직을 수행하게 됨으로 DAO의 변경이 있더라도 컨트롤러 클래스의 코드의 변경은 최소화 되기 때문에 유지보수 능력이 향상 되기때문입니다.




5. UserController 구현



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.web.nuri.controller;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
 
import com.web.nuri.user.UserDTO;
import com.web.nuri.user.UserService;
 
@Controller
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @RequestMapping(value="/login.do",method=RequestMethod.GET)
    public ModelAndView loginView(ModelAndView mav) {
        System.out.println("Controller.loginView() 호출");
        mav.setViewName("login");
        return mav;
    }
    @RequestMapping(value="/insertUser.do",method=RequestMethod.GET)
    public ModelAndView insertUserView(ModelAndView mav) {
        System.out.println("Controller.insertUserView() 호출");
        mav.setViewName("insertUser");
        return mav;
    }
    @ResponseBody
    @RequestMapping(value="/checkId.do")
    public int idCheck(UserDTO udto,ModelAndView mav) {
        System.out.println("Controller.idCheck() 호출");
        int result=0;
        UserDTO user=userService.getUser(udto);
        if(user!=null) result=1;
        else System.out.println("아이디사용가능");
        return result;
    }
    @ResponseBody
    @RequestMapping(value="/checkNickName.do")
    public int nickNameCheck(UserDTO udto,ModelAndView mav) {
        System.out.println("Controller.nickNameCheck() 호출");
        int result=0;
        UserDTO user=userService.getNickNameUser(udto);
        if(user!=null) result=1;
        return result;
    }
    @RequestMapping(value="/insertUser.do",method=RequestMethod.POST)
    public ModelAndView insertUser(UserDTO udto,ModelAndView mav) {
        System.out.println("Controller.insertUser() 호출");
        userService.insertUser(udto);
        mav.setViewName("login");
        return mav;
    }
}
 
cs


여기서 ajax의 실시간 아이디 중복체크에 쓰이는 메소드는 "/checkId.do"(동일 아이디가 있는지 체크)와 "/checkNickName.do"(동일 닉네임이 있는지 체크) url 매핑입니다. 각 메소드에 @ResponseBody 어노테이션이 붙은 이유는 jsp파일에서 넘어온 데이터를 이용해 로직을 처리하고 다시 jsp로 가공된 데이터를 넘길때 자동으로 json객체로 데이터를 바인딩해서 보내기 위해 붙인 어노테이션입니다.





6. pom.xml



1
2
3
4
5
6
            <!-- jackson2 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.7.2</version>
        </dependency>
cs


Controller에서 @ResponseBody를 이용하기 위해 jackson2 라이브러리를 받아줍니다.

(기타 applicationContext.xml , presentation-layer.xml 등의 스프링 설정파일은 Spring 카테고리의 Spring으로 회원가입을 구현한 글을 참고하시면 됩니다.)




7. insertUser.jsp



55
5657
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<%@ page language="java" contentType="text/html; charset=EUC-KR"
    pageEncoding="EUC-KR"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css" rel="stylesheet">
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
    <script type="text/javascript" src="//maxcdn.bootstrapcdn.com/bootstrap/latest/js/bootstrap.min.js"></script>
    <style>
    body {
        background: #f8f8f8;
        padding: 60px 0;
    }
    
    #login-form > div {
        margin: 15px 0;
    }
 
</style>
<title>회원가입</title>
</head>
<body>
    <div class="container">
        <div class="col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">
            <div class="panel panel-success">
                <div class="panel-heading">
                    <div class="panel-title">환영합니다!</div>
                </div>
                <div class="panel-body">
                    <form action="insertUser.do" id="login-form" method="post">
                        <div>
                            <input type="email" class="form-control id" name="id" placeholder="Email" oninput="checkId()" id="checkaa" autofocus>
                        </div>
                        <div>
                            <input type="password" class="form-control pass" name="pw" placeholder="Password" oninput="checkPwd()">
                        </div>
                        <div>
                            <input type="password" class="form-control pass" name="pwConfirm" placeholder="Confirm Password" id="repwd" oninput="checkPwd()">
                        </div>
                        <div>
                            <input type="text" class="form-control nickname" name="nickName" id="nickname" placeholder="Your Nickname" oninput="checkNick()" autofocus>
                        </div>
                        <div>
                            <button type="submit" class="form-control btn btn-primary signupbtn" disabled="disabled">회원가입</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
    <script>
 
    //아이디와 비밀번호가 맞지 않을 경우 가입버튼 비활성화를 위한 변수설정
    var idCheck = 0;
    var nickCheck = 0;
    var pwdCheck = 0;
    //아이디 체크하여 가입버튼 비활성화, 중복확인.
    function checkId() {
        var inputed = $('.id').val();
        console.log(inputed);
        $.ajax({
            data : {
                id : inputed
            },
            url : "checkId.do",
            success : function(data) {
                if(inputed=="" && data=='0') {
                    $(".signupbtn").prop("disabled"true);
                    $(".signupbtn").css("background-color""#aaaaaa");
                    $("#checkaa").css("background-color""#FFCECE");
                    idCheck = 0;
                } else if (data == '0') {
                    $("#checkaa").css("background-color""#B0F6AC");
                    idCheck = 1;
                    if(idCheck==&& pwdCheck == 1) {
                        $(".signupbtn").prop("disabled"false);
                        $(".signupbtn").css("background-color""#4CAF50");
                    } 
                } else if (data == '1') {
                    $(".signupbtn").prop("disabled"true);
                    $(".signupbtn").css("background-color""#aaaaaa");
                    $("#checkaa").css("background-color""#FFCECE");
                    idCheck = 0;
                } 
            }
        });
    }
  //재입력 비밀번호 체크하여 가입버튼 비활성화 또는 맞지않음을 알림.
    function checkPwd() {
        var inputed = $('.pass').val();
        var reinputed = $('#repwd').val();
        console.log(inputed);
        console.log(reinputed);
        if(reinputed=="" && (inputed != reinputed || inputed == reinputed)){
            $(".signupbtn").prop("disabled"true);
            $(".signupbtn").css("background-color""#aaaaaa");
            $("#repwd").css("background-color""#FFCECE");
        }
        else if (inputed == reinputed) {
            $("#repwd").css("background-color""#B0F6AC");
            pwdCheck = 1;
            if(idCheck==&& pwdCheck == 1) {
                $(".signupbtn").prop("disabled"false);
                $(".signupbtn").css("background-color""#4CAF50");
            }
        } else if (inputed != reinputed) {
            pwdCheck = 0;
            $(".signupbtn").prop("disabled"true);
            $(".signupbtn").css("background-color""#aaaaaa");
            $("#repwd").css("background-color""#FFCECE");
            
        }
    }
    //닉네임과 이메일 입력하지 않았을 경우 가입버튼 비활성화
    function checkNick() {
        var nickname = $("#nickname").val();
        console.log(nickname);
        $.ajax({
            data : {
                nickName : nickname
            },
            url : "checkNickName.do",
            success : function(data) {
                if(nickname=="" && data=='0') {
                    $(".signupbtn").prop("disabled"true);
                    $(".signupbtn").css("background-color""#aaaaaa");
                    $("#nickname").css("background-color""#FFCECE");
                    nickCheck = 0;
                } else if (data == '0') {
                    $("#nickname").css("background-color""#B0F6AC");
                    nickCheck = 1;
                    if(nickCheck ==&& pwdCheck == 1) {
                        $(".signupbtn").prop("disabled"false);
                        $(".signupbtn").css("background-color""#4CAF50");
                    } 
                } else if (data == '1') {
                    $(".signupbtn").prop("disabled"true);
                    $(".signupbtn").css("background-color""#aaaaaa");
                    $("#nickname").css("background-color""#FFCECE");
                    nickCheck = 0;
                } 
            }
        });
    }
    /*캔슬버튼 눌렀을 눌렀을시 인풋박스 클리어
    $(".cancelbtn").click(function(){
            $(".id").val(null);
            $(".pass").val('');
            $(".signupbtn").prop("disabled", true);
            $(".signupbtn").css("background-color", "#aaaaaa");
    });*/
    
   </script>
</body>
</html>
 
cs


여기서 밑에 보이는 function checkId() 함수가 실시간 아이디 중복체크를 위한 함수입니다. var inputed에 입력된 id 값을 받습니다. 그리고 ajax 코드안에 json형태의 데이터(id:inputed)로 /checkId.do url에 실시간으로 아이디값을 보내게 됩니다. 그러면 controller 안에 idCheck 메소드가 실행되게 됩니다. 그래서 만약 입력한 아이디가 존재한다면 리턴 값으로 1 값을 리턴하게 됩니다. 그러면 리턴된 데이터가  success : function(data){..} 함수의 data라는 매개변수로 값이 들어가게 됩니다. 그리고 그 매개변수로 조건문을 실행하게 되어 각 조건에 맞는 분기를 하게 됩니다. 그래서 아이디가 존재한다면 input 태그의 backgroud 색이 빨간색, 사용 할 수 있는 id인 경우 초록색으로 css를 변경하게 됩니다. 비밀번호의 경우는 jsp 파일 내에서 모두 체크를 하게 됩니다. 그래서 두번의 비밀번호의 입력이 같은 경우 input태크의 background 색이 초록색으로 변하게 됩니다. 마지막으로 닉네임 체크 같은 경우는 아이디 중복 체크와 모든 로직이 동일함으로 실시간 아이디 체크와 동일하게 코드 해석이 가능합니다.




8. 실행화면




이미 아이디가 존재하고 패스워드 두개가 다른 값을 가지며 또한, 이미 사용중인 닉네임인 경우 모든 input태그의 background 색이 빨간색으로 변함과 동시에 회원가입 버튼이 disabled가 된 것을 볼 수 있습니다.





사용가능한 아이디이며, 두개의 패스워드가 같고 또한 사용가능한 닉네임인 경우 모두 input 태그가 초록색으로 변하고 회원가입 버튼 또한 사용할 수 있게 disabled가 false로 변하게 됩니다.



9. 마치며



혹시 잘못된 점이 있으면 많은 지적부탁드립니다... 혹시 코드가 전체 필요하신 분은 댓글로 남겨주시면 확인하는 즉시 바로 보내드리겠습니다~!



posted by 여성게
: