'전체 글'에 해당되는 글 357건

  1. 2018.02.20 :: 그래프 DFS 와 BFS
  2. 2018.02.17 :: Spring 오브젝트와 의존관계 용어 정리
  3. 2018.02.16 :: ajax_실시간 아이디 중복 체크 구현 13
  4. 2018.02.16 :: Spring(스프링) 회원가입 6
  5. 2018.02.05 :: Spring으로 카카오톡 챗봇만들기 27


그래프 DFS(깊이우선탐색) 와 BFS(너비우선탐색)



1. 시작전


그래프를 구현하는 방법 중에는 크게 두 가지가 있습니다. 첫번째는 인접행렬을 이용하는 방법이고, 두번째는 인접리스트를 이용하는 방법입니다. 이 두가지 방법중 인접행렬을 이용한 DFS 와 BFS를 구현해보겠습니다.






2.DFS(깊이우선탐색)




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
package graph;
 
import java.util.Stack;
 
public class DFS {
    class Vertex{
        public char label;
        public boolean visited;
        public Vertex(char label) {
            this.label=label;
            this.visited=false;
        }
    }
    private final int maxVertices=20;
    private Vertex[] vertexList;
    private int Matrix[][];
    private int vertexCount;
    private Stack stack;
    public DFS() {
        vertexList=new Vertex[maxVertices];
        Matrix=new int[maxVertices][maxVertices];
        vertexCount=0;
        for(int y=0;y<maxVertices;y++) {
            for(int x=0;x<maxVertices;x++) {
                Matrix[x][y]=0;
            }
        }
        stack=new Stack();
    }
    public void addVertex(char label) {
        vertexList[vertexCount++]=new Vertex(label);
    }
    public void addEdge(int start, int end) {
        Matrix[start][end]=1;
        Matrix[end][start]=1;
    }
    public void displayVertex(int v) {
        System.out.println(vertexList[v].label);
    }
    public void dfs(int n) {
        vertexList[n-1].visited=true;
        displayVertex(n-1);
        stack.push(n-1);
        while(!stack.isEmpty()) {
            int v=getAdjUnvisitedVertex((int) stack.peek());
            if(v==-1) stack.pop();
            else {
                vertexList[v].visited=true;
                displayVertex(v);
                stack.push(v);
            }
        }
    }
    public int getAdjUnvisitedVertex(int v) {
        for(int j=0;j<vertexCount;j++) {
            if(Matrix[v][j]==&& vertexList[j].visited==falsereturn j;
        }
        return -1;
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        DFS d=new DFS();
        d.addVertex('1');
        d.addVertex('2');
        d.addVertex('3');
        d.addVertex('4');
        d.addVertex('5');
        d.addVertex('6');
        d.addVertex('7');
        
        d.addEdge(0,1); //1->2
        d.addEdge(1,3); //2->4
        d.addEdge(0,2); //1->3
        d.addEdge(2,4); //3->5
        d.addEdge(2,5); //3->6
        d.addEdge(2,6); //3->7
        //1번 정점부터 방문시작
        d.dfs(1);
    }
 
}//결과 : 1 2 4 3 5 6 7
 
cs


설명:  1번 정점부터 시작하므로 스택에 1번정점을 넣어준다. 그리고 스택에서 peek을 하여 1번정점을 꺼내어 1번정점에 대한 인접행렬의 행을 탐색하여 첫번째로 나오는 visited가 false인 연결정점(2번정점)을 visited=true로 바꿔주고 스택에 넣어준다. 그리고 다시 스택에서 peek을 하여 이전에 넣었던 2번정점을 읽어와 2번정점에 대한 인접행렬 행을 읽어 첫번째로 나오는 visited가 false인 연결정점(4번정점)을 visited=true로 바꿔 주고 스택에 넣어준다. 이런 과정을 진행하다가 막다른 지점이 나오면(4번정점) 그에 대한 인접행렬의 행에서 visited가 false 인접한 정점이 존재하지 않으므로 스택에서 pop해주어 백트래킹을 진행한다. 그러다 1번정점에서 다시 visited가 false 인접정점이 존재하므로 위의 과정을 반복하게 된다.





3.BFS(너비우선탐색)


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
package graph;
 
 
import java.util.LinkedList;
import java.util.Queue;
 
import graph.DFS.Vertex;
 
public class BFS {
    class Vertex{
        public char label;
        public boolean visited;
        public Vertex(char label) {
            this.label=label;
            this.visited=false;
        }
    }
    private final int maxVertices=20;
    private Vertex[] vertexList;
    private int Matrix[][];
    private int vertexCount;
    private Queue queue;
    public BFS() {
        vertexList=new Vertex[maxVertices];
        Matrix=new int[maxVertices][maxVertices];
        vertexCount=0;
        for(int y=0;y<maxVertices;y++) {
            for(int x=0;x<maxVertices;x++) {
                Matrix[x][y]=0;
            }
        }
        queue=new LinkedList();
    }
    public void addVertex(char label) {
        vertexList[vertexCount++]=new Vertex(label);
    }
    public void addEdge(int start, int end) {
        Matrix[start][end]=1;
        Matrix[end][start]=1;
    }
    public void displayVertex(int v) {
        System.out.println(vertexList[v].label);
    }
    public void bfs(int n) {
        vertexList[n-1].visited=true;
        displayVertex(n-1);
        queue.offer(n-1);
        int v2;
        while(!queue.isEmpty()) {
            int v1=(int) queue.poll();
            while((v2=getAdjUnvisitedVertex(v1))!=-1) {
                vertexList[v2].visited=true;
                displayVertex(v2);
                queue.offer(v2);
            }
        }
    }
    public int getAdjUnvisitedVertex(int v) {
        for(int j=0;j<vertexCount;j++) {
            if(Matrix[v][j]==&& vertexList[j].visited==falsereturn j;
        }
        return -1;
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        BFS d=new BFS();
        d.addVertex('1');
        d.addVertex('2');
        d.addVertex('3');
        d.addVertex('4');
        d.addVertex('5');
        d.addVertex('6');
        d.addVertex('7');
        
        d.addEdge(0,1); //1->2
        d.addEdge(1,3); //2->4
        d.addEdge(0,2); //1->3
        d.addEdge(2,4); //3->5
        d.addEdge(2,5); //3->6
        d.addEdge(2,6); //3->7
        d.bfs(1);
    }
 
}//결과: 1 2 3 4 5 6 7
 
cs


설명: 시작정점 1번을 방문후 큐에 삽입해준다. 그리고 큐에서 poll을 해서 1번정점을 빼주고 1번노드 다음레벨에 해당하는(인접하고있는) 정점들을 모두 방문 후 큐에 삽입해준다.(2번정점 3번정점) 그리고 큐에서 poll을 해서 2번정점을 빼주고 2번 정점 다음레벨에 해당하는 정점들을 모두 큐에 삽입해준다.(4번정점) 그다음 큐에서 poll을 해 3번 정점을 빼주고 3번 정점 다음레벨에 해당하는 정점들을 모두 큐에 삽입해준다.(5,6,7번정점).. 이과정을 반복해준다.





4. 백준 1260번 DFS와 BFS  




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
package graph;
 
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
import java.util.Stack;
 
public class DFSBFS {
    class Vertex{
        public String label;
        public boolean visited;
        public Vertex(String label) {
            this.label=label;
            this.visited=false;
        }
    }
    private final int maxVertices=1000;
    private Vertex[] vertexList;
    private int Matrix[][];
    private int vertexCount;
    private Stack stack;
    private Queue queue;
    public DFSBFS() {
        vertexList=new Vertex[maxVertices];
        Matrix=new int[maxVertices][maxVertices];
        vertexCount=0;
        for(int y=0;y<maxVertices;y++) {
            for(int x=0;x<maxVertices;x++) {
                Matrix[x][y]=0;
            }
        }
        stack=new Stack();
        queue=new LinkedList();
    }
    public void addVertex(String label) {
        vertexList[vertexCount++]=new Vertex(label);
    }
    public void addEdge(int start, int end) {
        Matrix[start][end]=1;
        Matrix[end][start]=1;
    }
    public void displayVertex(int v) {
        System.out.print(vertexList[v].label+" ");
    }
    public void dfs(int n) {
        vertexList[n-1].visited=true;
        displayVertex(n-1);
        stack.push(n-1);
        while(!stack.isEmpty()) {
            int v=getAdjUnvisitedVertex((int) stack.peek());
            if(v==-1) stack.pop();
            else {
                vertexList[v].visited=true;
                displayVertex(v);
                stack.push(v);
            }
        }
        System.out.println();
        for(int j=0;j<vertexCount;j++) {
            vertexList[j].visited=false;
        }
    }
    public void bfs(int n) {
        vertexList[n-1].visited=true;
        displayVertex(n-1);
        queue.offer(n-1);
        int v2;
        while(!queue.isEmpty()) {
            int v1=(int) queue.poll();
            while((v2=getAdjUnvisitedVertex(v1))!=-1) {
                vertexList[v2].visited=true;
                displayVertex(v2);
                queue.offer(v2);
            }
        }
        System.out.println();
        for(int j=0;j<vertexCount;j++) {
            vertexList[j].visited=false;
        }
    }
    public int getAdjUnvisitedVertex(int v) {
        for(int j=0;j<vertexCount;j++) {
            if(Matrix[v][j]==&& vertexList[j].visited==falsereturn j;
        }
        return -1;
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner sc=new Scanner(System.in);
        DFSBFS ma=new DFSBFS();
        int n=sc.nextInt();
        int m=sc.nextInt();
        int v=sc.nextInt();
        
        for(int i=0;i<n;i++) {
            ma.addVertex(i+1+"");
        }
        while(m!=0) {
            ma.addEdge(sc.nextInt()-1,sc.nextInt()-1);
            m--;
        }
        ma.dfs(v);
        ma.bfs(v);
    }
 
}
 
cs


posted by 여성게
:
IT이론 2018. 2. 17. 21:53


스프링의 오브젝트와 의존관계에 관한 용어 정리



1. 관심사의 분리


책임이 다른 코드를 분리해서 두 개의 클래스로 만드는 것.

ex) 보통의 DAO 클래스에서 DB커넥션 관련 코드와 DB에서 실제로 수행되는 statement, resultset 등의 코드가 혼재되어있다. 즉, db커넥션관련 책임과 db에서 실제로 수행되는 코드의 책임은 엄연히 다른 책임을 가진다. 이런 코드를 관심사의 분리로써 db커넥션관리 코드를 별도의 class로 분리하여 DAO내에서는 DB커넥션과 관련된 코드자체를 분리해내는 작업(리팩토링)을 한다.


결론적으로 관심이 같은 것은 한 객체 또는 친한 객체로 관심이 다른 것은 가능한 멀리 떨어져 서로 영향을 주지 않게 코드를 분리&확장하는 이론이다.




2. 전략패턴


자신의 기능 맥락에서, 필요에 따라 변경이 필요한 기능은 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 클래스를 필요에 따라 바꿔서 사용 할 수 있게 하는 디자인패턴을 말한다.

ex) DAO(컨텍스트), DaoTest(클라이언트,DAO테스트를 위해 DAO클래스를 사용하는 클라이언트), DBConnection(전략, Connection인터페이스를 사용할 db종류에 따라 구현한 클래스)

컨텍스트(dao)를 사용하는 클라이언트(daotest)는 컨텍스트가 사용하는 전략(connection 구현 클래스)을 컨텍스트의 생성자에 인터페이스 타입으로 제공해주는 것이 일반적이다.


즉, db의 종류가 바뀌어서 connection구현 클래스가 바뀌더라도 그 connection을 사용하는 dao클래스의 코드변경은 필요가 없다.




3. 개방폐쇄원칙


위의 전략패턴을 이용해 자신의 책임 자체가 변경되는 경우 외에는 불필요한 변화가 발생하지 않도록 막아주고, 자신이 사용하는 외부 오브젝트(connection구현클래스)의 기능은 자유롭게 확장하거나 변경할 수 있게 만든다.




4. 제어의 역전/IoC


오브젝트가 생성되고 여타 오브젝트와 관계를 맺는 작업의 제어권을 별도의 오브젝트 팩토리에게 책임을 넘긴다. 그 말은 오브젝트가 자신이 사용할 오브젝트(connection구현 클래스)를 스스로 선택하지 않는다. 또 자신(dao)도 어떻게 만들어지고 어디서 사용되는지 알 수 없다. 왜냐면 모든 제어의 권한을 자신이 아닌 다른 대상(프레임워크, 스프링컨테이너)에게 위임하기 때문이다.




5.  싱글톤 레지스트리


우선은 전통적인 싱글톤은 private 생성자를 가지기 때문에 상속이 불가능하다. 그리고 서버 환경에서 여러 JVM에 분산되서 설치가 되는 경우, 하나의 오브젝트만 생성된다는 보장이 없고, 전역 상태로 빠질 수 있기에 바람직하지 않다. 그래서 많은 학자들도 싱글톤 디자인패턴을 꺼려하는 디자인패턴 혹은 주의해서 사용해야하는 디자인패턴으로 생각하고 있다. 이런 단점을 해결하고 스프링에서 적용하기 위해 애플리케이션 컨텍스트는 싱글톤 레지스트리로 되어있다. 스프링이 직접 싱글톤 오브젝트를 생성하고 관리하는 기능을 제공하는 데, 그때 생성되는 오브젝트는 스태틱 메소드와 private 생성자를 사용하지 않는 일반 클래스를 싱글톤으로 활용하게 해주기 때문에 싱글톤의 단점을 보완한 싱글톤으로 사용할 수 있게 해준다. 즉, 서버에서 사용되는 서비스 오브젝트로서의 장점을 살릴 수 있는 싱글톤을 사용하면서도 전통 싱글톤 패턴의 단점을 극복할 수 있도록 설계된 컨테이너를 활용할 수 있게 되었다.( 빈 클래스는 싱글톤으로 생성되기 때문에 인스턴스 변수가 존재해서는 안된다. 여러 사용자 스레드에 의해서 인스턴스 변수가 변경되어 잘못된 인스턴스 변수가 다른 스레드가 사용할 수 있기 때문이다. 하지만 의존주입을 위한 읽기전용 변수는 사용이 가능하다. 왜냐하면 컨테이너에 의해 싱글톤 오브젝트로 의존주입되기 때문이다.)



6. 의존관계 주입/DI


설계 시점과 코드에는 클래스와 인터페이스 사이의 느슨한 의존관계(낮은 결합도)만 만들어놓고, 구체적인 클래스 의존관계를 알수 없게 하며 런타임시에 실제 사용할 구체적인 의존 오브젝트를 제 3자(컨테이너)의 외부도움으로 내부에 주입받아서 다이나믹한 의존관계를 갖게 해주는 IoC의 특별한 케이스이다. 즉, IoC와 DI는 엄연히 다른 용어이다. 스프링의 IoC의 대표적인 동작원리가 의존관계주입(DI)인 것이다.



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 여성게
:
Web/Spring 2018. 2. 16. 00:30

Spring(스프링) 회원가입



1. 개발환경



1.eclipse

2.tomcat 8.0

3.spring 4.3.7

4.oracle






2. 프로젝트 파일구조









3. 관련 라이브러리 추가.



위에서 aop weaver 같은 경우는 pointcut에 걸렸을 때, advice 메소드를 호출해 실행하는 것이 weaver 라고 합니다. 그 기능을 하기 위해 weaver 라이브러리를 등록합니다.

parser 같은 경우는 repository가 있는 경로에 한글이 낄 경우 이상한 에러(log어쩌고 저쩌고)가 뜨게 됩니다.(ex:C:\Users\윤여성\.m2\repository....) 




그래서 한글 인코딩을 하기위한 라이브러리라고 생각하면 됩니다.






4.web.xml





처음에 있는 context-param의 경우는 서블릿이 컨트롤러를 호출하게 될때, 그 컨트롤러 코드에 있는 비즈니스단의 클래스의 빈생성(UserService)을 위해 미리 preload(ContextLoaderListner이용)하는 경우입니다. 그리고 그 다음은 presentation-layer.xml 파일을 로딩하여 해석한 servlet을 생성하는 코드입니다. 마지막으로 filter는 post방식으로 넘어오는 데이터의 한글 인코딩을 위한 코드입니다.



5. presentation-layer.xml




처음의 컴포넌트 스캔은 컨트롤러에 있는 어노테이션들을 인식해 빈 생성을 하기위한 코드이고, 그 다음의 리졸버는 컨트롤러에서 리턴하는 경로의 앞과 뒤에 붙여줄 문자를 설정하는 코드입니다.






6. applicationContext.xml




mybatis를 사용하기 위한 sql세션팩토리빈 생성과 세션템플릿 설정입니다. 그 다음은 트랜잭션 매니저를 빈등록하여 txAdvice를 등록해 밑의 pointcut에 걸려있는 메소들중 에러가 날 경우 자동으로 rollback을 하여 디비에 잘못된 칼럼이 등록되는 것을 방지하는 트랜잭션 매니저입니다. 여기서 advisor가 행위를 하기 위해 아까 라이브러리에 weaver라는 라이브러리를 등록한 것 입니다.




7. sql-map-config.xml , user-mapping.xml , database.properties



sql-map-config.xml 의 첫번째 alias는 user-mapper.xml에서 파라미터타입이나 리절트타입에 사용하기 위한 설정입니다. 그러면 user-mapper.xml 에서의 코드가 훨씬 간결해지는 것을 볼 수 있습니다.




8. UserDTO, UserDAO ,UserService , UserServiceImpl 구현



인스턴스 변수를 선언해주고 각각의 getter, setter 메소드를 선언해주면 됩니다. alt+shift+s 를 통해 겟터,셋터메소드 및 toString() 메소드를 쉽게 생성할 수 있습니다.


@Repository 어노테이션을 통해 applicationContext.xml 에 있는 context:component-scan 설정에 걸려 UserDAO 빈 생성을 하게합니다. 그리고 @Autowired 를 통해 applicationContext.xml 에 있는 sqlSessionTemplate 객체의 의존주입을 컨테이너에게 맡기게 됩니다. 그리고 각 메소드에 있는 mybatis.~은 모두 user-mapper.xml에 정의 해놓은 sql 구문들을 사용하는 sqlSessionTemplate의 메소드입니다.



UserService 클래스입니다. 보시면 UserDAO에 있는 메소드들이 추상메소드로 들어간 것이 보이시죠? 즉, UserService 객체의 다형성을 이용하기 위해 인터페이스 타입으로 클래스를 생성한 것입니다.(컨트롤러에서 직접 DAO에 접근하는 것이 아니라 Service 구현 클래스를 통해 DAO에 접근한다.) 하나 팁을 주자면 UserDAO에서 alt+shift+t 를하면 extract interface라는 기능이 있습니다. 그러면 UserDAO에 들어가있는 메소드를 모두 추상메소드 타입으로 갖고 있는 인터페이스를 쉽게 생성할 수 있습니다.(UserDAO에 생기게 되는 implements UserService는 꼭 지워줄것!)



UserService 인터페이스를 상속받아 구현한 클래스입니다.




9. UserController 구현



위에 @Autowired에 UserService 인터페이스 타입으로 의존주입을 받는 이유는 유지보수 이유때문입니다. 객체지향의 다형성을 이용하게 된다면 UserServiceImpl가 변경되어도 의존주입을 하게 되는 UserService와는 상속관계가 지속되기에 별도의 컨트롤러의 코드의 변경이 없어도 되게됩니다. 그리고 login.do 와 insertUser.do 매핑 메소드에 RequestMethod를 GET과 POST 두개를 나눈 이유는 index페이지에서 로그인 혹은 회원가입 버튼을 누르게 된다면 get방식으로 요청(진짜 로그인과 회원가입 같은 경우는 post 방식으로 데이터를 서버측으로 보내게 됩니다.)이 가서 각 페이지로 이동하게 됨으로 페이지 이동을 위한 메소드는 별도의 url매핑을 만들필요 없이 같은 url매핑으로 메소드 방식만 두개로 정해주면 됩니다. 




10. insertUser.jsp




<%@ 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(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");
 
                    $("#nickname").css("background-color""#FFCECE");
 
                    nickCheck = 0;
 
                } 
 
            }
 
        });
 
    }
 
     
 
   </script>
 
</body>
 
</html>
cs





11. 마치며..


insertUser.jsp 와 UserController에 있는 코드 중 ajax를 이용한 실시간 아이디 중복체크 및 비밀번호 확인, 닉네임 체크 코드가 들어가있습니다. 그것은 별도로 따로 설명을 올리겠습니다.. 두서 없이 한 설명을 들어주셔서 감사합니다. 코드를 원하시는 분은 댓글로 남겨주시면 보내드리겠습니다.






posted by 여성게
:
ChatBot 2018. 2. 5. 14:37

Spring프레임워크를 이용한 챗봇


이제 카카오톡 이전 자동응답 시스템은 deprecated 될 예정입니다. 현재 신규 사용도 불가하구요. 추후에 현재나온 카카오톡 챗봇 빌더를 이용하여 챗봇 개발하는 글을 올릴 예정이니 그때 다시 확인해주시면 감사하겠습니다. - 여성게 20190729


1.시작전



코딩 초보인 저자가 무엇을 만들까 생각 해보다가 생각한 것이 챗봇이었습니다. 초보인 제가 하루종일 헤매다가 만들 챗봇을 저처럼 코딩에 익숙치 않으신 분들도 쉽게 만들어 볼 수 있도록 글을 작성해보았습니다.(틀린부분 지적질좀 부탁드려요...)






2.개발환경



1.구름IDE(구름을 사용하는 이유는 서버를 단 몇번의 클릭으로 생성 할 수 있다. 편한만큼 불편한 점도 많다. 단순히 테스트용으로 사용하길..)

2.eclipse

3.Spring Framework 4.3.3








3.Jackson2 라이브러리 추가






4./keyboard 구현하기


/keyboard 에 매칭되는 기능입니다. 이용자가 최초로 채팅방에 들어 올때 기본으로 키보드 영역에 표시될 메뉴들을 설정하기 위함입니다.

카카오톡 관리자 홈페이지에서 API형 URL을 등록 할때 초기 테스트로 꼭 필요한 기능이니 꼭 구현해주셔야합니다.

키보드 응답구조입니다.

위 구조와 같이 플러스친구와 1:1대화 누르면 나오는 첫화면인 /keyboard 와 매칭되는 JSON 객체의 구조입니다.

type은 buttons형과 text형이 있지만 저자는 buttons형이 편했음으로..




KeyboardDTO 구현


json-simple 라이브러리를 받아 아예 컨트롤러 안에서 keyboard 객체를 json object 형태로 리턴해주어도 되지만 객체지향인만큼 keyboard객체를 선언해서 keyboard 객체를 리턴해주면 조금더 깔끔할 것 같기에... 여기 하나더 보아야 할것.. DTO의 변수와 JSON 구조에서의 변수명이 같은 것을 주목해주세요! (같아야 나중에 어노테이션으로 인한 자동매핑이 가능합니다.)



controller 구현


컨트롤러 구현 이전에 스프링 설정파일에 설정 해 놓아야할 것이 있다.


스프링설정파일 설정


위에 보듯이 컨트롤러에서 선언한 어노테이션을 인식하기 위한 설정들이다. 빈생성 및 json을 위한 어노테이션 인식을 위함이다.(자세한 것은 구글링...)


Controller-/keyboard 


위에서 주목해야 할 어노테이션은 @RestController 이다. @RestController=@Controller + @ResponseBody 라는 뜻이다. 즉, 모든 메소드에서 @ResponseBody를 추가 해줄 필요가 없다는 것이다. (여기서 @Controller가 붙은 클래스는 컨테이너에 의해서 컨트롤러로 인식됨과 동시에 객체를 자동 생성해주는 어노테이션이고, @ResponseBody는 리턴 값을 자동으로 JSON 객체로 변환해서 리턴해주는 기능의 어노테이션이다.!)




이제 /keyboard url이 요청되었을 때, 카카오톡이 아닌 웹에서의 결과를 보면, json 형태로 결과값들이 리턴되는 것을 볼수 있다.("여보 이거 눌러봐 !"는 여자친구를 위해...저것을 눌렀을 때, 사랑해라는 대답을 응답 하기위해...해놓은 소소한 이벤트였다.. 애교로 봐주시길..)




여러분들이 직접만든 플러스친구에게 1:1대화를 신청하면 첫 화면에서 이렇게 keyboard가 생성 된 것을 볼 수 있다.

하지만 이 버튼들중 무엇을 눌러도 에러가 발생 할 것이다. 왜냐? 버튼을 눌러 플러스친구에게 메시지를 보내면 그 메시지에 대한 응답을 할 메소드를 만들지 않았기 때문...




사용자 요청 구조 


이것은 이용자가 보낸 메시지가 서버에 저런 json 구조로 값이 들어 온다는 겁니다.

사용자 요청에 따른 챗봇의 응답구조



이것은 서버측에서 이용자가 보낸 메시지를 토대로 응답을 보낼때의 json의 구조입니다. 보시면 json객체 안에서 message & keyboard 객체가 또 message 객체 안에 여러 객체가 들어가 있는 것이 보입니다. 반드시 이 객체들을 모두 구현 할 필요는 없습니다. 예를 들어 텍스트로만 응답을 보내고 싶으면 message 객체안의 text라는 것만 객체에 담아서 보내도 됩니다. 그 예는 조금 있다 보실수 있습니다.




MessageButtonDTO,MessageDTO,PhotoDTO,RequestMessageDTO,ResponseMessageDTO구현





여기서 RequestMessageDTO 객체는 사용자로부터 들어오는 요청에 대한 DTO이고(위 구조 참고), ResponseMessageDTO는 사용자의 요청에 대한 응답을 담아서 보낼 DTO 객체입니다. 그 외의 DTO들은 ResponseMessageDTO에 담길 객체들입니다.(이것 또한 위의 구조참고) 이것들을 활용한 controller의 구현입니다.




Controller-/message



여기서 주목해야 할것은 /message 요청이 들어왔을 때, 수행되는 메소드의 매개변수 입니다. @RequestBody 어노테이션을 이용해 json객체 형태의 사용자의 요청을 RequestMessageDTO 객체에 매핑을 시켜주는 겁니다. 그래서 위에서 설명했듯이 JSON 구조와 DTO 객체의 인스턴스 변수명을 똑같이 맞춰주는 겁니다. 그래야 자동으로 JSON객체를 RequstMesaageDTO 객체의 형태로 자동으로 바꿔줘서 매개변수에 들어오는 겁니다. 그리고 if 문 안의 dto.getContent() 메소드는 요청에 담긴 사용자의 String 형태의 채팅내용을 가져오는 겁니다.(위 요청 구조에서 content라는 것이 있었던 거 기억하죠?)

이런식으로 사용자가 한 채팅내용에 따라 적절히 분기 시켜 각각의 응답을 결정합니다....(원래 진정한 챗봇은 이런 구조를 갖지 않겠죠..? 자연어 처리가 들어가야하는데 필자는 그정도의 능력이 안되 간단히 단어만 추출해 답변을 정하는 정도..)

마지막으로 어떠한 조건문에 걸려 응답에 대한 객체들을 적당히 만들어주고 마지막에 ResponseMessageDTO 객체에 담아서 리턴해주면 JSON 형태로 응답이 가게 됩니다.(위에 보면 응답구조에 따른 모든 json객체를 다 포함해서 리턴하지 않습니다. 자신이 텍스트만 보내고 싶으면 텍스트만 리턴해주면 되고 사진도 보내고 싶으면 사진 객체도 포함시키고 개발자의 마음입니다.)





마치며.. 




필자는 코딩을 잘하지 않습니다.. 다만 제가 개발하면서 정말 헤매었던 부분, 어려웠던 부분을 위주로 저와같은 초보도 알아 듣기 쉽게 하기 위해 노력했습니다... 잘못된 부분은 지적해주시면 감사합니다. 부족한 필자의 글을 보고 아주 조금이나마 도움이 되셨다면 좋겠습니다...


혹시나 코드가 필요하신분들이 있다면 말씀해주세요~!





'ChatBot' 카테고리의 다른 글

Chatbot - 1.개요_ 우리말 한글 챗봇 구성요소  (1) 2019.07.06
posted by 여성게
: