JAVA 웹개발 과정 국비 28일차 정리

28일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


네트워크와 JDBC를 이용하여 프로그램을 만들어보았다.

1. 관리시스템 - 예제

마찬가지로 아직 배우는 단계이기 때문에 main에서 예외 전가를 해준다.

1) members 테이블 생성

SQL Developer에서 테이블을 먼저 생성해준다.
비밀번호를 VARCHAR(128)로 만드는 이유는, 길이가 128자리인 16진수로 암호화를 해주는 SHA512 암호화 해싱 함수를 이용할 것이기 때문이다.

CREATE TABLE members (
    id VARCHAR(20) PRIMARY KEY,
    pw VARCHAR(128) NOT NULL,
    name varchar(20) NOT NULL,
    signup_date DATE DEFAULT SYSDATE NOT NULL
);

2) Client 클래스

관리시스템을 만들 때 콘솔창에 안내문구를 출력해야한다.
여기서 서버에서 안내문구를 받아올 것인가, Client 클래스에서 콘솔에 출력할 것인가가 헷갈렸다. 난 당연히 서버 프로그램이니까 서버에서 출력해야지! 라고 생각했는데, Client 클래스에서 출력하는 것이 낫다고 한다.

관리 시스템 출력을 서버에서 하면?

  • 장점 : 클라이언트가 메뉴 등을 업데이트 할 필요가 없어진다.
  • 단점 : 서버가 처리할 코드가 많아 트래픽이 증가한다. (치명적)

서버는 최소한의 데이터만 클라이언트로 보내주는 것이 1: N 관계에서 안정성을 가지기 때문에 웹 어플리케이션 개발 패턴이 서버의 부하를 최소한으로 줄이는 경향을 가진다.
따라서 장점이 그다지 좋지 않고, 단점이 크기 때문에 클라이언트에서 안내문을 출력한다.

① 소켓 생성 ~ 안내문 틀 생성

try with resource 문으로 Socket, DataInputStream, DataOutputStream, Scanner를 모두 받아준다. 이들의 공통점은 close를 꼭 해줘야 한다는 것이다.

그리고 관리 시스템 문구를 출력해주고, menu 변수와 if문으로 각각의 기능을 만들어 준다.

try(Socket sock = new Socket("127.0.0.1",22000);
		DataInputStream dis = new DataInputStream(sock.getInputStream());
		DataOutputStream dos = new DataOutputStream(sock.getOutputStream());
		Scanner sc = new Scanner(System.in);){

	while(true) {
		System.out.println("== 관리 시스템 ==");
		System.out.println("1. 로그인");
		System.out.println("2. 회원가입");
		System.out.println("3. 종료");
		System.out.print(">> ");

		String menu = sc.nextLine();
		dos.writeUTF(menu);

		if (menu.equals("1")) {

		}else if (menu.equals("2")) {

		}else if (menu.equals("3")) {
			System.out.println("시스템을 종료합니다.");
			System.exit(0);
		}else {
			System.out.println("메뉴를 다시 확인하세요.");
		}
	}
}
	

② 로그인 기능 만들기

ID와 PW를 각각 입력받아 변수에 저장하고, 서버로 dos.writeUTF 메소드와 flush 메소드로 전달한다.
dos.writeUTF("login")코드는 클라이언트가 어떤 메뉴를 선택해서 데이터를 서버에 넘길지 알려주는 일종의 menu변수 역할을 한다고 생각하면 된다.
입력받은 menu 변수를 서버로 보내도 동일하지만, 서버에 1이나 2가 전달된다면 가독성이 떨어진다는 단점이 있으므로 login이나 signup 등의 문자열을 이용하여 전달하면 더 좋은 코드를 만들 수 있다.

loginResult는 서버에서 login메소드를 사용하여 리턴받은 결과 값을 받는 변수이다.
if문으로 나누어 성공, 실패를 출력한다.

System.out.print("ID : ");
String id = sc.nextLine();
System.out.print("PW : ");
String pw = sc.nextLine();
dos.writeUTF("login");
dos.writeUTF(id);
dos.writeUTF(pw);
dos.flush();

int loginResult = dis.readInt();
if (loginResult>0) {
	System.out.println("로그인 성공!");
}else {
	System.out.println("로그인 실패..");
}

③ 회원가입 기능 만들기

로그인 기능과 마찬가지로 ID, PW, 이름을 입력 받고 서버로 전송한다.
이 때, 이미 있는 ID라면 서버에서 id 중복 검사를 하고 boolean으로 결과값을 전송받는다.
true이면 사용중인 id이므로, continue를 써서 다음 반복(메뉴)으로 넘어가고, 아니라면 서버에서 회원가입을 진행한 후 마찬가지로 결과값을 전송받아 if문에 따라 성공유무를 출력해준다.

System.out.print("사용할 ID를 입력하세요 : ");
String id = sc.nextLine();

System.out.print("사용할 PW를 입력하세요 : ");
String pw = sc.nextLine();

System.out.print("이름을 입력하세요 : ");
String name = sc.nextLine();

dos.writeUTF("signup");
dos.writeUTF(id);
dos.writeUTF(pw);
dos.writeUTF(name);
dos.flush();

// 중복검사 결과 확인
boolean isIdExist = dis.readBoolean();
if(isIdExist) {
	System.out.println("이미 사용중인 ID 입니다.");
	continue;
}
					
int result = dis.readInt();
if (result>0) {
	System.out.println("회원가입을 성공하였습니다.");
}else {
	System.out.println("회원가입에 실패하였습니다.");
}

3) Server 클래스

① 소켓 생성 ~ 안내문 틀 생성

Client 클래스와 마찬가지로 만들어준다.
위에서 언급했듯이, menu의 입력값인 1, 2 대신 login, signup 등의 문자열로 if문을 생성한다.

MembersDAO dao = new MembersDAO(); 
System.out.println("연결 대기중 입니다..");

try(ServerSocket server = new ServerSocket(22000);
		Socket sock = server.accept();
		DataInputStream dis = new DataInputStream(sock.getInputStream());
		DataOutputStream dos = new DataOutputStream(sock.getOutputStream());){
	while(true) {
		String cmd = dis.readUTF();

		if (cmd.equals("login")) {
			
		}else if (cmd.equals("signup")) {
			
		}
	}
}

② 로그인 기능 만들기

클라이언트가 입력한 ID와 PW를 받아온다. 여기서 PW는 민감한 정보이기때문에 암호화 메소드를 이용해 암호화해준 값을 받는다.
dao의 login 메소드를 구현하여 MembersDTO 자료형으로 맞는 로그인 정보를 가져온 뒤, ID와 PW가 일치하면 클라이언트에게 1을 전송, 아니라면 -1을 전송한다.

String id = dis.readUTF();
String pw = EncryptUtils.getSHA512(dis.readUTF());
System.out.println(id + ":" + pw);

MembersDTO dto = dao.login(id);
if (id.equals(dto.getId()) && pw.equals(dto.getPw())) {
	dos.writeInt(1);
}else {
	dos.writeInt(-1);
}
dos.flush();

③ 회원가입 기능 만들기

ID, PW(암호화 된), 이름을 전송받아 변수에 넣어준다.
중복 검사 isIdExist 메소드로 boolean 값을 리턴받은 뒤 이 boolean 값을 전송한다.

중복 id가 존재한다면 continue로 입력단계를 건너 뛰고, 없다면 insert 메소드를 사용하여 MembersDTO 인스턴스를 생성하여 DB에 입력한다.
여기서 try-catch문으로 혹시나 생길지 모르는 예외를 대비한다.
또한 입력 성공 유무를 클라이언트에게 알려주어야 하므로 결과값을 클라이언트에 전송한다.

String id = dis.readUTF();
String pw = EncryptUtils.getSHA512(dis.readUTF());
String name = dis.readUTF();
System.out.println(id + " : " + pw + ":" + name);

// 중복검사  
boolean isIdExist = dao.isIdExist(id);
dos.writeBoolean(isIdExist);
dos.flush();

if(isIdExist) {continue;}

try {
	int result = dao.insert(new MembersDTO(id, pw, name, null));
	dos.writeInt(result);
}catch(Exception e) {
	e.printStackTrace();
	dos.writeInt(-1);
}
dos.flush();

4) DTO 클래스

DB와 동일하게 멤버필드를 만들어준다.
Date 자료형은 sql.Date를 import하는 것에 유의한다.
getter, setter, constructor를 생성해주면 끝.

private String id;
private String pw;
private String name;
private Date signup_date;

5) DAO 클래스

extract 메소드 만들기

기존에 만들었던 getConnection 메소드를 그대로 만들어준다.

private Connection getConnection() throws Exception {
		
	Class.forName("oracle.jdbc.driver.OracleDriver");
		
	String url = "jdbc:oracle:thin:@localhost:1521:xe";
	String id = "practice";
	String pw = "practice";
		
	return DriverManager.getConnection(url, id, pw);
}

② insert 메소드 만들기

try with resource 문으로 자동 close되게 만들어주고, dto 인스턴스를 파라미터로 받아 각각 쿼리문에 넣어주면 깔끔한 코딩을 할 수 있다.
insert 쿼리이므로 int 값으로 result 값을 리턴한다.

public int insert(MembersDTO dto) throws Exception {
		
	String sql = "INSERT INTO members VALUES (?,?,?,DEFAULT)";
	try(Connection con = this.getConnection();
			PreparedStatement pstat = con.prepareStatement(sql);){
		pstat.setString(1, dto.getId());
		pstat.setString(2, dto.getPw());
		pstat.setString(3, dto.getName());
		int result = pstat.executeUpdate();
		con.commit();
		return result;
	}
}

③ isIdExist 메소드 만들기

id 중복을 확인하는 메소드이다.
같은 id를 가진 레코드가 있는지 SELECT문으로 확인하고, rs.next()를 리턴하면 boolean으로 있는지 없는지 확인이 가능하다.

public boolean isIdExist(String pid) throws Exception {
	String sql = "SELECT * FROM members WHERE id = ?";
	try(Connection con = this.getConnection();
			PreparedStatement pstat = con.prepareStatement(sql);){
		pstat.setString(1, pid);			
		try(ResultSet rs = pstat.executeQuery();){
			return rs.next();
 		}
	}
}

④ login 메소드 만들기

id를 파라미터로 받아 그 id를 가진 레코드를 MembersDTO 인스턴스로 리턴한다.
null 값으로 먼저 생성해준 뒤, rs.next()가 있다면 새 인스턴스를 생성하여 리턴하고, 없다면 그대로 null을 리턴한다.

boolean으로 id와 pw를 받아 리턴하는 login 메소드를 만들 수도 있지만, 나중에가면 로그인 한 대상의 정보가 어차피 필요해지므로 인스턴스를 리턴해주는 메소드를 만드는 것이 더 좋다.

public MembersDTO login(String pid) throws Exception {
	String sql = "SELECT * FROM members WHERE id = ?";
	try(Connection con = this.getConnection();
			PreparedStatement pstat = con.prepareStatement(sql);){
		pstat.setString(1, pid);			
		try(ResultSet rs = pstat.executeQuery();){
			MembersDTO dto = null;
			if (rs.next()) {
				String id = rs.getString("id");
				String pw = rs.getString("pw");
				String name = rs.getString("name");
				Date signup_date = rs.getDate("signup_date");
				dto = new MembersDTO(id,pw,name,signup_date);
			}
			return dto;
 		}
	}	
}

6) EncryptUtils 클래스

비밀번호를 암호화해주는 static 메소드를 위한 클래스이다.
현업에서도 자주 사용하는 SHA-512 함수를 구글링으로 코드를 복사해 온 뒤, 그대로 적용시키면 된다.
String 값을 받아서 암호화 된 String 값을 리턴한다.

import java.math.BigInteger;

public class EncryptUtils {

	public static String getSHA512(String input){
		String toReturn = null;
		try {
			MessageDigest digest = MessageDigest.getInstance("SHA-512");
			digest.reset();
			digest.update(input.getBytes("utf8"));
			toReturn = String.format("%0128x", new BigInteger(1, digest.digest()));
		} catch (Exception e) {
			e.printStackTrace();
		}
		return toReturn;
	}

	public static void main(String[] args) {
		String result = getSHA512("ABC");
		System.out.println(result);
		System.out.println(result.length());
	}
}

댓글남기기