기말 프로젝트를 어찌어찌 잘 넘겼다. (이미 종강했다)
진짜 너무 힘들었다... 팀 세명 중이 둘이서만 코드를 쳐야 했고 소켓 통신을 처음 해보면서 처음 구조부터 엉망으로 짜기 시작해서 나중엔 하드코딩으로 디비를 이용해 메시지를 찍어내야 했다...ㅋㅋ

종강하고 나서 여유롭게 일주일을 쉬고 나니 이 프로젝트 전의 나와 후의 나를 돌아보니 많은 것이 바뀌어 있었다.
예전에는 잘 못 느꼈던 성취감을 이번에는 확실하게 맛봤다.
그래서 앞으로의 프젝이 참 기대가 된다.


 

홈페이지 메인화면

메인 프레임은 다른 팀원분이 해주셨다.
그분이 프런트단을 다 해주셔서 나는 백엔드에 전념할 수 있었다.
나중엔 AWS를 사용해서 디비를 만들어 사용했다.
기존에 21-1 방학 프로젝트를 할 때 사용하던 RDS 서버가 있었는데 그걸 활용했다.
나중에 생각해보니 굳이 디비를 서버에 올리지 않아도 됐을 거 같은데... JDBC로 하드 코딩해서 어떻게든 되었을 거 같다. 


 

로그인, 회원가입 화면

이 화면도 다른 팀원분이 프런트를 맡아서 해주셨다.
기능은 내가 만들어둔 메서드가 있어서 그대로 끌어서 사용했으면 되는데 그 과정 중에 merge conflict가 발생해 브랜치를 삭제해야 하는 상황이 생겼다.
그분은 협업이 처음이었고, 나는 경험이 있었지만 내가 주도하면서 하는 프로젝트는 처음이었다.
(그동안은 초보자 수준의 프로젝트를 해왔었고, 서로 친한 친구사이어서 알아서 코드를 공유하고 깃을 제대로 쓰지도 않았다.)
그래서 서로 합이 잘 맞지 않아서 조심히 행동했었던 거 같다.
지금 생각해보면 그렇게 행동한 것이 정말 잘한 일이라 생각한다.
프로젝트가 끝나고 보니 그전에 힘들고 서운했던 마음은 없어지고 뿌듯하고 한층 가까워진 기분이 들었다.
난 프로젝트가 끝나고 코딩이 더 재미있어졌다.


채팅방

채팅 관련한 기능은 내가 맡았다.
채팅에 사용되는 클라이언트 소켓과 서버 소켓을 만들고 AWS에서 RDS 서버를 사용해서 메시지를 각각의 방에 맞춰서 넣었다.
그 과정 중엔 나와 막역한 사이인 선배님이 쿼리문 짜는 것을 도와주었다.
(클라이언트에서 보낸 메시지를 서버에서도 표시할 수 있게 하는 기능을 도와주셨지만 안타깝게도 내가 처음에 짠 구조 때문에 하드코딩이 먹히질 않았다. 역시 나무를 베기 전엔 도끼부터 갈아야 한다...)


데이터베이스 구조

그에 비해 데이터베이스는 참 이쁘게 짰다. 사용한 프로그램은 exerd이다.


 

package DB;

import java.net.InetAddress;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;

import dto.Member;
public class Login {
	 Scanner sc = new Scanner(System.in);
	 
	 /**
	  * 회원가입 기능
	  */
	public void SignIn() {
		try {
			InetAddress local = InetAddress.getLocalHost();
			String ip = local.getHostAddress();
			DBUtil util = new DBUtil();
			Connection conn = null;
			conn = util.open();
			PreparedStatement ps = null;
			System.out.print("학번을 입력하세요 : ");
			int studentNo = sc.nextInt();
			System.out.print("\n사용할 아이디를 입력하세요 : ");
			String id = sc.next();
			System.out.print("\n사용할 비밀번호를 입력하세요 : ");
			String password = sc.next();
			System.out.print("\n본명을 입력하세요 : ");
			String name = sc.next();
			System.out.print("\n학년을 입력하세요 : ");
			int grade = sc.nextInt();
			System.out.print("\n전공을 입력하세요 : ");
			String subject = sc.next();
			String job = "";
			if (grade > 4 || grade < 1) {
				job = "학생";
			}else {
				System.out.print("\n현재 직업을 입력하세요 : ");
				job = sc.next();
			}
			String sql = "INSERT INTO MEMBER(STUDENTNO, ID, PASSWORD, NAME, GRADE, SUBJECT, IP, JOB)\r\n"
					+ "VALUES ("+studentNo+", '"+id+"', '"+password+"', '"+name+"', "+grade+", '"+subject+"', '"+ip+"', '"+job+"');";
			ps = conn.prepareStatement(sql);
			ResultSet rs = null;
			rs = ps.executeQuery();
			util.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 로그인 기능
	 * @param member
	 */
	public Member memberLogin(Member member) {
		Member loginMember = new Member();
		try {
			System.out.println(member.getId());
			System.out.println(member.getPassword());
			DBUtil util = new DBUtil();
			Connection conn = null;
			conn = util.open();
			PreparedStatement ps = null;
			ResultSet rs = null;
			String sql = "SELECT STUDENTNO, ID, NAME, GRADE, SUBJECT, JOB FROM MEMBER WHERE ID = ? AND PASSWORD = ?";
			ps = conn.prepareStatement(sql);
			ps.setString(1, member.getId());
			ps.setString(2, member.getPassword());
			rs = ps.executeQuery();
			while(rs.next()) {
				loginMember.setStudentNo(rs.getInt("STUDENTNO"));
				loginMember.setId(rs.getString("ID"));
				loginMember.setName(rs.getString("NAME"));
				loginMember.setGrade(rs.getInt("GRADE"));
				loginMember.setSubject(rs.getString("SUBJECT"));
				loginMember.setJob(rs.getString("JOB"));
			}
			util.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("123123123" + loginMember.getName());
		return loginMember;
		
	}
}

회원가입 기능과 로그인 기능이다.
JDBC를 이용해서 디비 상에 데이터를 추가하는 과정이다.
내가 기능을 만들어 뒀으니 여기서 System.out.println은 어차피 콘솔 상에만 보이니까 신경 쓰지 말고 메서드를 가져다 쓰면 된다는 말을 했었는데 다른 팀원분이 그냥 직접 만드시고 쿼리문만 복사해서 가져가셨다...
(나중에 쿼리문이 안된다고 말하시길래 다시 다 확인하고 업로드했었다. 근데 다른 팀원분이 커밋을 올리면서 이미 컨플릭트가 난 상황에서 올리려 하니 안 올라가니까 내 브랜치에 올려버리셨다... 그래서 결국 한 브랜치는 날려버리고 다른 브랜치는 롤백을 해서 기존의 코드를 삭제해서 맞췄다.)
난 처음에는 정말 정말 화가 났었다.
근데 결국엔 풀 리퀘스트 방식을 채택하지 않은 내 판단 미스라는 것이 잘못이었다. 그냥 그런 관리 없이도 할 수 있다 생각했는데 그건 잘못된 생각이었다. 결국 코드 리뷰는 필수적이라는 걸 다시 한번 느꼈다.


 	try {
			DBUtil util = new DBUtil();
			Connection conn = null;
			conn = util.open();
			PreparedStatement ps = null;
			ResultSet rs = null;
			String sql = "SELECT ROOMNO FROM (SELECT LISTAGG(STUDENTNO, ',') WITHIN GROUP(ORDER BY ROOMNO, STUDENTNO) AS CLIENT, ROOMNO FROM PARTICIPANTS GROUP BY ROOMNO) WHERE CLIENT = ?|| ',' ||?";
			ps = conn.prepareStatement(sql);
			ps.setInt(1, client.getStudentNo() > member.getStudentNo() ? member.getStudentNo() : client.getStudentNo());
			ps.setInt(2, client.getStudentNo() < member.getStudentNo() ? member.getStudentNo() : client.getStudentNo());
			rs = ps.executeQuery();
			int roomNo = 0;
			if (rs.next()) {
				roomNo = rs.getInt("ROOMNO");
			}
			System.out.println("1 : " + roomNo);
			if (roomNo == 0) {
				sql = "INSERT INTO CHATTING_ROOM (ROOMNO, ROOMNM) VALUES (CHATTING_ROOM_SQ.NEXTVAL, '" + title + "')";
				ps = conn.prepareStatement(sql);
				ps.executeQuery();
				sql = "INSERT INTO PARTICIPANTS VALUES(PARTICIPANTS_SQ.NEXTVAL, ?, CHATTING_ROOM_SQ.CURRVAL)";
				ps = conn.prepareStatement(sql);
				ps.setInt(1, loginMember.getStudentNo());
				ps.executeQuery();
				ps = conn.prepareStatement(sql);
				ps.setInt(1, clientMember.getStudentNo());
				ps.executeQuery();
			} else {
				sql = "SELECT NAME, CONTENT FROM CHATTING C INNER JOIN MEMBER M ON C.STUDENTNO = M.STUDENTNO WHERE C.ROOMNO = ? ORDER BY CHATINDEX";
				ps = conn.prepareStatement(sql);
				ps.setInt(1, roomNo);
				rs = ps.executeQuery();
				while (rs.next()) {
					String name = rs.getString("NAME");
					String content = rs.getString("CONTENT");
					String message = name + " : " + content + "\n";
					chatGround.append(message);
				}
			}

			util.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

개인적으로 참 잘 짰다고 생각하는 코드이다.
LISTAGG를 사용해서 기존에 대화했었던 방을 그대로 다시 들어갈 수 있게 하는 기능이었다.
한마디로 카톡에서 방을 한번 만들면 그 방에 메시지가 그대로 남아있는 기능을 구현했다고 보면 된다.
(이걸 발표날에 어필했어야 하는데, 발표자는 따로 있어서 나는 내가 시연만 하는 줄 알았는데 시연과 발표도 같이 하게 되면서 준비를 하나도 못했었다. 다음엔 설마 하겠어하지 말고 좀 준비하자..ㅋㅋ) 


@Override
	public void keyPressed(KeyEvent e) {
		try {
			if (e.getKeyCode() == e.VK_ENTER) {
				InetAddress local = InetAddress.getLocalHost();
				String ip = local.getHostAddress();
//				String ip = "192.168.219.102";
				socket = new Socket(ip, 1593);
				String data = chatInput.getText();
				ObjectOutputStream osw = new ObjectOutputStream(socket.getOutputStream());
//				List<HashMap<Member, String>> message = new ArrayList<HashMap<Member, String>>();
				List<MessageVO> message = new ArrayList<MessageVO>();
				MessageVO messagevo = new MessageVO();
				messagevo.setMember(member);
				messagevo.setText(data);
//				HashMap<Member, String> text = new HashMap<Member, String>();
//				text.put(member, data);
//				message.add(text);
				message.add(messagevo);
				osw.writeObject(message);
				chatInput.setText("");
				chatGround.append(messagevo.getMember().getName() + " : " + messagevo.getText() + "\n");
//				chatGround.append(member.getName() + " : " + message.get(0).get(member) + "\n");
				DBUtil util = new DBUtil();
				Connection conn = null;
				conn = util.open();
				PreparedStatement ps = null;
				ResultSet rs = null;

				String sql = "SELECT ROOMNO FROM (SELECT LISTAGG(STUDENTNO, ',') WITHIN GROUP(ORDER BY ROOMNO, STUDENTNO) AS CLIENT, ROOMNO FROM PARTICIPANTS GROUP BY ROOMNO) WHERE CLIENT = ? || ',' ||?";
				ps = conn.prepareStatement(sql);
				ps.setInt(1,
						client.getStudentNo() > member.getStudentNo() ? member.getStudentNo() : client.getStudentNo());
				ps.setInt(2,
						client.getStudentNo() < member.getStudentNo() ? member.getStudentNo() : client.getStudentNo());
				rs = ps.executeQuery();
				int roomNo = 0;
				if (rs.next()) {
					roomNo = rs.getInt("ROOMNO");
				}
				System.out.println("2 : " + roomNo);
				sql = "INSERT INTO CHATTING VALUES(CHATTING_SQ.NEXTVAL, ?, ?, ?)";
				ps = conn.prepareStatement(sql);
				ps.setInt(1, member.getStudentNo());
				ps.setString(2, data);
				ps.setInt(3, roomNo);
				rs = ps.executeQuery();
				sql = "SELECT NAME, CONTENT FROM CHATTING C INNER JOIN MEMBER M ON C.STUDENTNO = M.STUDENTNO WHERE C.ROOMNO = ? ORDER BY CHATINDEX";
				ps = conn.prepareStatement(sql);
				ps.setInt(1, roomNo);
				rs = ps.executeQuery();
				while (rs.next()) {
					String name = rs.getString("NAME");
					String content = rs.getString("CONTENT");
					String messages = name + " : " + content + "\n";
					chatGround.append(messages);
				}
			}

		} catch (Exception e2) {
			e2.printStackTrace();
		}
	}

얼마나 급하게 마감을 쳤으면 아직도 지우지 못한 테스트 코드가 남아있을까?
아마 깃 허브에서 코드를 보면 알겠지만, 프로젝트가 정말 지저분하다. 
얼기설기 이어 붙여서 키메라를 만든 거 같다... 돌아가긴 하는데 보기엔 안 좋은...
다음에는 좀 더 기획단계에서부터 세세하게 잡고 들어가야겠다.
어떤 기능을 언제까지 한다는 데드라인만 정하는 게 아니라 어떤 기능을 만들 때 어떤 구조로 어떤 데이터를 넣을 건지, 나중에 연결할 때는 어떻게 돌아가게끔 할 것인지 말이다.


여기서부터 이제 코드 몽키 시간이다.

@Override
	public void keyReleased(KeyEvent e) {
		try {
			if (e.getKeyCode() == e.VK_ENTER) {
//				InetAddress local = InetAddress.getLocalHost();
//				String ip = local.getHostAddress();
////				String ip = "192.168.219.102";
//				socket = new Socket(ip, 1593);
				String data = chatInput.getText();
//				ObjectOutputStream osw = new ObjectOutputStream(socket.getOutputStream());
////				List<HashMap<Member, String>> message = new ArrayList<HashMap<Member, String>>();
//				List<MessageVO> message = new ArrayList<MessageVO>();
//				MessageVO messagevo = new MessageVO();
//				messagevo.setMember(member);
//				messagevo.setText(data);
////				HashMap<Member, String> text = new HashMap<Member, String>();
////				text.put(member, data);
////				message.add(text);
//				message.add(messagevo);
//				osw.writeObject(message);
//				chatInput.setText("");
//				chatGround.append(messagevo.getMember().getName() + " : " + messagevo.getText() + "\n");
////				chatGround.append(member.getName() + " : " + message.get(0).get(member) + "\n");
				DBUtil util = new DBUtil();
				Connection conn = null;
				conn = util.open();
				PreparedStatement ps = null;
				ResultSet rs = null;

				String sql = "SELECT ROOMNO FROM (SELECT LISTAGG(STUDENTNO, ',') WITHIN GROUP(ORDER BY ROOMNO, STUDENTNO) AS CLIENT, ROOMNO FROM PARTICIPANTS GROUP BY ROOMNO) WHERE CLIENT = ? || ',' ||?";
				ps = conn.prepareStatement(sql);
				ps.setInt(1,
						client.getStudentNo() > member.getStudentNo() ? member.getStudentNo() : client.getStudentNo());
				ps.setInt(2,
						client.getStudentNo() < member.getStudentNo() ? member.getStudentNo() : client.getStudentNo());
				rs = ps.executeQuery();
				int roomNo = 0;
				if (rs.next()) {
					roomNo = rs.getInt("ROOMNO");
				}
				System.out.println("2 : " + roomNo);
				sql = "INSERT INTO CHATTING VALUES(CHATTING_SQ.NEXTVAL, ?, ?, ?)";
				ps = conn.prepareStatement(sql);
				ps.setInt(1, member.getStudentNo());
				ps.setString(2, data);
				ps.setInt(3, roomNo);
				rs = ps.executeQuery();
				sql = "SELECT NAME, CONTENT FROM CHATTING C INNER JOIN MEMBER M ON C.STUDENTNO = M.STUDENTNO WHERE C.ROOMNO = ? ORDER BY CHATINDEX";
				ps = conn.prepareStatement(sql);
				ps.setInt(1, roomNo);
				rs = ps.executeQuery();
				while (rs.next()) {
					String name = rs.getString("NAME");
					String content = rs.getString("CONTENT");
					String messages = name + " : " + content + "\n";
					chatGround.append(messages);
				}
			}

		} catch (Exception e2) {
			e2.printStackTrace();
		}
	}


나는 이 프로젝트를 하면서 정말 큰 문제에 봉착했다.
서버에서 클라이언트로 메시지를 보내면 바로 채팅창에 올라오는데 그 반대로 클라이언트에서 서버로 메시지를 보내면 서버의 채팅창은 아무 반응이 없는 것이다.
사실 이 문제는 소켓을 하나 더 열어서 클라이언트끼리 통신을 하게 만들면 되는 일이었는데 그 당시의 나는 클라이언트 소켓을 여러 개를 열 수 있다는 사실을 몰랐다.
그래서 이 코드가 탄생했다.
디비에서 채팅 내역을 긁어오게 만든 것이다.
그것도 스레드를 사용한 것도 아닌.. 진짜 코드 몽키 같은 짓을 말이다.
정말 부끄럽지만 다신 이런 짓을 안 하려고 여기 기록한다...
다음부터는 진짜 스레드를 쓰자..


곧 2021년이 지나간다.
나는 참 운이 좋은 사람이다.
나를 도와주는 사람들 덕분에 올해는 정말 많은 성장을 이뤄냈다.
성장하면서 나의 2022년 목표가 많이 생겼다.
내년에도 힘내자!

제출일자: 2021.09.28
소요시간: 5일가량

배운 점: 먼저 Swing과 AWT 둘 중에서 어떤 것을 메인 프레임으로 정할지를 정해야 했다. ( Frame, JFrame ) 실습만 한 상태에서 프로젝트를 진행하니 생성자 부분에서 어디까지 패널을 구현해야 하는지 막막했고 paint 함수 한 개 가지고 그래픽을 찍어내야 하는데 함수 한 개에 그림 한 개를 하기도 하는 등 총체적 난국이었다. 직접 부딪혀보니 패널이 없어도 이미지를 찍어낼 수 있었고 패널을 쓰는 것보다 그냥 이미지를 찍어서 좌표를 가지고 충돌 구현도 하고 위치도 바꾸고 하는 게 나았다.

고칠 점: DRY 원칙을 많이 위배했고, 객체를 만드는데 에러가 심했다. ArrayList로 몬스터 물고기들을 관리하니 한 마리 먹을 때 다른 물고기들이 바뀌는 오류가 있었고, 바뀐 물고기 때문에 보이는 이미지 크기와 실제 크기 값이 달라 끔살을 당하기도 했다. YAGNI 원칙을 위배한 것은 깃으로 관리를 안 했다는 점이다. 다음 프로젝트부터는 깃에서 처음 만들기 시작해서 버전 관리를 해야 할 것 같다. 특히 브랜치를 만들어 개발 환경과 배포 환경을 나누는 것이 중요할 것 같다. 같은 기능을 하는 함수를 두 개로 나눠놓은 부분이 있었다. 사실 합치는 게 맞는데 시간도 없고 에러가 뜰 거 같아 내버려 뒀다. 이러지 말자!! 물고기를 더 추가하고 싶었지만 첫 번째 이유로 에러를 감당할 수 없어 세 마리로 만족했다. (처음 프로토타입으로 만족했다는 뜻)

프로젝트 발표 

PPT 표지

 

개인적으로 좀 시간이 부족하기도 하고 Swing을 처음 접하다 보니 클래스 구조도 엉망이고.. 참 여러모로 아쉬운 프로젝트였다. 클래스 하나에 이너 클래스를 두 개씩 넣고 메서드를 만들어서 겨우 구분해놨지만 좀 맘에 들지는 않는다.
실제로 파일도 두 개 밖에 없다. Fish.java 와 GameMain.java이다.

 

게임 내의 기능

실제로 별거 없다..ㅋㅋ 사실 물고기를 더 많이 넣고 싶었는데.. 먹히는 물고기들을 ArrayList에 넣고 인덱스로 구분해서 돌리다 보니 물고기가 서로 바뀌는 사태가 발생했다.. 
해결하려고 객체를 다시 다 수작업으로 넣기도 했지만 시간 상의 문제로 일단 3마리만 넣게 되었다. 
(근데 저 세 마리만 있는데도 저 사태는 벌어지더라..)

 

개발 중 벌어진 일들..

이 프로젝트를 진행하면서 Swing과 AWT 충돌 때문에 두 번이나 갈아엎고 다시 시작했다. 
이 둘은 정말 잘 맞지가 않았다. ( 내 주관적인 평가이다. )
처음엔 Frame으로 토대를 만들고 JPanel 이랑 JLabel을 추가했는데 도저히 프레임 위에 띄울 수가 없었다. 
물론 삽질을 하면서 간신히 올리는 데엔 성공했는데 이젠 KeyListener를 받아먹질 않았다..

그래서 다른 방법은 없을까 하다가 찾은 게 Application Window 였다.

이클립스 Marketplace에서 WindowBuilder를 다운로드 받으면 된다.

 

편한 작업환경, 눈에 바로 보이는 시원한 preview

 

근데 이거로 하는 건 더더욱 성에 차질 않았다. 패널 가지고 노는 게 이렇게 어려울 줄이야!
처음 기본 구조만 따라 치다가 결국 버렸다.
그래서 패널 없이 하는 게 낫겠다 싶어 paint 함수를 이용해 drawImage()로 이미지를 올려버리는 방식을 택했다. 
물론 패널은 한 개는 들어가긴 했지만 아마 없어도 잘 돌아갈 것 같다.


public void paint(Graphics g) {
	g.drawImage(backIm, 0, 0, null);
//	super.paintComponents(g); // delete background image (use when check the image is on frame)  
//	g.drawRect(playerFish.getX() + 10, playerFish.getY() + 30, playerFish.getWidth() - 20,
//			playerFish.getHeight() - 50); // crash space 
	g.drawImage(player_fish, playerFish.getX(), playerFish.getY(), playerFish.getWidth(), playerFish.getHeight(),
			null);
	for (int i = 0; i < mob.size(); i++) {
		g.drawImage(list.get(i), mob.get(i).getX(), mob.get(i).getY(), mob.get(i).getWidth(),
				mob.get(i).getHeight(), null);
	}
}

이렇게 이미지들을 패널에 넣지 않고 그냥 그대로 올렸다.

이미지
패널
프레임

이런 방식으로 쌓아 올렸다.

'Java > Java Swing' 카테고리의 다른 글

21-2 프로젝트 수업 마무리  (0) 2021.12.28

+ Recent posts