본문 바로가기

수업 정리

55~58일차(회원가입과 로그인)

**요청 순서

요청-> Controller-> Service-> Dao-> Repository-> Dao-> Service-> Controller-> Data-> View(웹: ajax로 Data를 가져와서 사용)

    => Controller에서 Data까지가 REST API 서버(Back End)

    => 요청과 View가 FrontEnd

 

**회원가입과 로그인

    => 회원가입과 로그인을 REST API Server를 구축해서 구현

    => 회원가입 화면 제작이나 가입 요청(ajax)하는 부분 등 HTML 출력부분을 제외한 모든 처리에서 사용가능한 서버

    => 파일 업로드도 같이 처리 : 파일업로드는 구현후 상대경로를 이용하는 것으로 변경

1. 데이터베이스 작업

  1) 테이블 생성

 

  2) 샘플데이터 작성

    => ID, 닉네임 중복 검사를 위해서 작성

 

  3) 테스트가 종료되면 샘플데이터 삭제

    => 데이터 삽입시 비밀번호를 암호화 하여 삽입하는데 이전에 입력한 데이터는 암호화가 되어있지 않아 로그인 안됨

 

2. Dynamic WebApplication을 생성

    => web.xml(웹프로젝트 설정파일- Web Application이 실행되면 이 파일의 내용을 읽어서 프로젝트 전체를 설정. 이 파일이 잘못되면 Applicaiton이 시작되지 않는 경우가 많음) 파일을 포함하도록 생성 - 포함시키지 않았으면 Servers 디렉토리에서 복사하여 WebContent/WEB-INF 디렉토리에 붙여넣기

    => WebContent는 루트 디렉토리의 이름으로 프로젝트 종류에 따라 다른 이름일 수 있음

    => 실제 배포가 될 때는 루트 디렉토리는 애플리케이션 이름으로 대체됨

 

3. 필요한 의존성 라이브러리들을 프로젝트에 복사

    => Dynamic Web Application은 라이브러리들이 WebContent/WEB-INF/lib 디렉토리에 복사가 되어야 함

    => servlet-api.jar : JDK SE버전을 가지고 웹 프로그래밍을 하려면 HttpServlet 클래스가 존재하는 라이브러리 필요

        (WAS가 가지고 있기 때문에 배포시에는 필요 없음)

    => jstl.jar : JSTL(c:if, c:forEach등)을 사용하기 위해 필요

        - 웹 서버만을 만드는 경우는 필요없음

        - jsp 파일에서 attribute를 출력하기 위해 사용하는 라이브러리이기 때문에 출력을 하지 않을 거라면 필요없음

    => json.jar : 자바의 데이터를 json 포맷으로 만들어주는 라이브러리

    => mysql-connector : MySQL 사용을 위한 라이브러리

 

4. 프로젝트 공통 설정

    => 데이터베이스 접속정보 설정

  1) META-INF 디렉토리에 context.xml 파일을 만들어서 접속정보를 작성

<?xml version="1.0" encoding="UTF-8"?>
<Context> 
  <Resource name="DBConn" 
   auth="Container"
   type="javax.sql.DataSource" 
   username="root" 
   password="900826"
   driverClassName="com.mysql.jdbc.Driver"
   factory="org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory"
   url="jdbc:mysql://localhost:3306/sample?useUnicode=true&amp;characterEncoding=utf8"
   maxActive="20" 
   maxIdle="300"/>  
</Context>

 

  2) web.xml 파일에 context.xml 파일의 내용을 읽어오는 코드를 작성

<!-- context.xml 파일의 내용을 읽어내는 설정 -->
<resource-ref>
	<description>Connection</description>
	<res-ref-name>DBConn</res-ref-name>
	<res-type>javax.sql.DataSource</res-type>
	<res-auth>Container</res-auth>
</resource-ref>

    => 프로젝트 설정으로 여러 개발자가 협업하는 경우 여기까지 프로젝트를 미리 만들어두고 다운로드 받아서 사용

 

5. User 테이블에 발생하는 요청을 처리할 자원들을 생성

    => url 패턴은 user디렉토리 패턴을 이용

        - 예전에는 디렉토리 패턴이 아니라 확장자 패턴을 많이 사용했는데 최근에는 디렉토리 패턴을 많이 사용

        - /user/*

    => view는 member라는 디렉토리에 저장 

        - view와 동일 디렉토리 패턴 제작시 view로 이동시도 Controller가 작업을 수행하므로 가능한 다른 디렉토리 이용

    => 필요한 자원 : DTO, DAO, Service, Controller 클래스

    => view 디렉토리는 실제 view와 그외 자원이 저장되는 디렉토리로 구분

        - 그외 자원 : css, js, 이미지, 사운드, 동영상 등)

        - resoureces라는 디렉토리를 만들고 그 안에 다시 각각의 디렉토리를 만들어서 자원을 저장

    => 만드는 순서는 상관없지만 DAO, Service, Controller는 순서대로 만듬

        - DAO를 주입받아 Service 사용, Service를 주입받아 Controller가 사용하기 때문

  1) User 테이블의 데이터를 저장할 DTO 클래스를 생성

    => domain.User

package domain;

import java.sql.Date;

public class User {
	private String email;
	private String password;
	private String nickname;
	private String image;
	private int islogin;
	private int isremove;
	private Date logindate;
	private Date joindate;
	private int isemail;
	
	public User() {
		super();
		// TODO Auto-generated constructor stub
	}

	public User(String email, String password, String nickname, String image, int islogin, int isremove, Date logindate,
			Date joindate, int isemail) {
		super();
		this.email = email;
		this.password = password;
		this.nickname = nickname;
		this.image = image;
		this.islogin = islogin;
		this.isremove = isremove;
		this.logindate = logindate;
		this.joindate = joindate;
		this.isemail = isemail;
	}

	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}

	public String getNickname() {
		return nickname;
	}
	public void setNickname(String nickname) {
		this.nickname = nickname;
	}

	public String getImage() {
		return image;
	}
	public void setImage(String image) {
		this.image = image;
	}

	public int getIslogin() {
		return islogin;
	}
	public void setIslogin(int islogin) {
		this.islogin = islogin;
	}

	public int getIsremove() {
		return isremove;
	}
	public void setIsremove(int isremove) {
		this.isremove = isremove;
	}

	public Date getLogindate() {
		return logindate;
	}
	public void setLogindate(Date logindate) {
		this.logindate = logindate;
	}

	public Date getJoindate() {
		return joindate;
	}
	public void setJoindate(Date joindate) {
		this.joindate = joindate;
	}

	public int getIsemail() {
		return isemail;
	}
	public void setIsemail(int isemail) {
		this.isemail = isemail;
	}

	@Override
	public String toString() {
		return "User [email=" + email + ", password=" + password + ", nickname=" + nickname + ", image=" + image
				+ ", islogin=" + islogin + ", isremove=" + isremove + ", logindate=" + logindate + ", joindate="
				+ joindate + ", isemail=" + isemail + "]";
	}
}

  2) 모든 DAO 클래스에서 공통으로 사용할 내용을 소유한 AbstractDAO 클래스를 생성하고 공통된 내용을 작성

    => 원래 이 클래스는 애플리케이션을 제작하며 리펙토링 단계에서 만들어야하지만 미리 생성

        - 이런 클래스를 만들어서 상속받아서 사용하도록 해주는 것이 라이브러리나 프레임워크 라고 함

    => 이 클래스는 인스턴스를 만들 필요가 없어 추상 클래스로 제작

        - 타 패키지에서의 사용을 막기 위해 default(package) 접근 지정자 사용

    => 공통 필요 내용 : Connection, PreparedStatement, ResultSet 3개의 속성, 연결, 해제와 관련된 2개의 메소드

    => 자신만의 application을 만들 때는 접근지정자를 default로 하고, 프레임워크 제작시 protected로 지정

package dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

public class AbstractDao {
	//데이터베이스 연동에 필요한 속성
	Connection con;
	PreparedStatement pstmt;
	ResultSet rs;
	
	//데이터베이스 연결 메소드
	void connect() {
		try {
			//web.xml 파일에서 데이터베이스 접속 정보를 가져오기
			Context context = new InitialContext();
			DataSource ds = (DataSource)context.lookup("java:comp/env/DBConn");
			
			//직접 접속하는 것이 아니라 이미 접속된 연결 객체를 빌려와서 사용
			con = ds.getConnection();
		}catch (Exception e) {
			System.err.println("연결 실패");
			System.out.println(e.getMessage());
			e.printStackTrace();
		}
	}
	//데이터베이스 자원을 해제하는 메소드
	void close() {
		try {
			if(rs != null)
				rs.close();
			if(pstmt != null)
				pstmt.close();
			if(con != null)
				con.close();
			
		}catch (Exception e) {
			System.err.println("접속 해제 실패");
			System.out.println(e.getMessage());
			e.printStackTrace();
		}
	}
}

 

  3) User 테이블의 데이터베이스 작업을 위한 UserDao 클래스를 생성

    => AbstractDao 클래스를 상속하도록 생성

    => dao.UserDao

package dao;

public class UserDao extends AbstractDao {
	//Singleton 패턴을 적용하기 위한 코드
	//인스턴스를 하나만 생성하는 디자인 패턴
	//모든 곳에서 공유할 데이터를 갖는 클래스나 Entry point(출입구)에 해당하는 클래스
	//또는 서버에서 클라이언트의 요청을 처리하는 클래스는 인스턴스가 1개면 되기 때문
	private UserDao() {}
	private static UserDao userDao;
	
	public static UserDao sharedInstance() {
		if(userDao == null) {
			userDao = new UserDao();
		}
		return userDao;
	}
}

 

  4) Business Logic(업무처리)을 처리해 줄 Service 클래스를 디자인

    => 싱글톤 패턴을 적용하고 템플릿 메소드(인터페이스 -> 클래스 : 가독성과 유지보수)을 적용

    => Service에서는 Dao를 사용

        - Dao 인스턴스가 필요

    => Service 패키지에 User의 요청을 처리해줄 메소드의 원형을 선언하는 인터페이스를 생성

        - Service

package service;

//User에 대한 처리를 하기 위한 메소드 선언을 위한 인터페이스
//메뉴판과 유사한 역할을 수행
public interface UserService {
	
}

    => UserService

package service;

import dao.UserDao;

//User의 요청을 처리할 메소드를 구현 할 클래스
public class UserServiceImpl implements UserService {
	//Service에서 사용할 UserDao 변수
	private UserDao userDao;
	
	private UserServiceImpl() {
		userDao = UserDao.sharedInstance();
	}
	private static UserService UserService;
	
	public static UserService sharedInstance() {
		if(UserService == null) {
			UserService = new UserServiceImpl();
		}
		return UserService;
	}
}

 

  5) 사용자의 요청을 받아 필요 서비스를 호출 및 결과를 출력할 View를 결정해주는 역할을 수행하는 Controller클래스를 생성

    => HttpServlet 클래스를 상속하여 만들어야 함

    => 처리할 URL 패턴을 설정해야 함

    => Service 인스턴스를 소유하고 있어야 함

    => controller.UserController를 생성하고 URL 패턴은 user 디렉토리 패턴으로 설정

package controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import service.UserService;
import service.UserServiceImpl;

@WebServlet("/user/*")
public class UserController extends HttpServlet {
	private static final long serialVersionUID = 1L;

	private UserService userService;
	
    public UserController() {
        super();
        userService = UserServiceImpl.sharedInstance();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String requestURI = request.getRequestURI();
		String contextPath = request.getContextPath();
		String command = requestURI.substring(contextPath.length());
		
		String method = request.getMethod();
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

}

 

  6) 여기까지가 하나의 서비스를 구현하기 위한 기본작업

    => 프레임워크 사용시 클래스를 만들고 변수를 생성하는 부분까지만 직접하고 나머지는 프레임워크가 구현

 

6. 시작 페이지 만들기

    => 대규모 웹 애플리케이션에서는 모든 요청이 Controller를 거치도록 하는 것을 권장

    => Web.xml의 webcome-file list라는 항목이 시작요청이 왔을 때 보여지는 페이지를 설정

        - 실제 존재하는 파일명이 설정되어야 함

    => Controller는 url 패턴으로 설정하고, web.xml은 파일이름으로 설정하기 때문에 시작 요청을 처리하기 어려움

    => web.xml 파일에 설정한 시작 페이지가 바로 Controller의 요청으로 포워딩하거나 리다이렉트 되도록 만듬

        - 이때 Controller가 index.html 도 같이 처리하는 경우가 많음

  1) Web.xml 설정 확인 후 index.jsp 파일 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>유저 관리</title>
</head>
<body>
	<!-- 바로 user/main으로 포워딩 하게 해주는 코드
	전자정보 프레임워크가 만들어주는 프로젝트에서 index.jsp에 보면 이런 코드가 존재 -->
	<!-- 유저 디렉토리가 들어가서 Controllor의 영향을 받음  -->
	<jsp:forward page="user/main"></jsp:forward>
</body>
</html>

 

  2) UserController가 index.html 요청도 처리할 수 있도록 URL 패턴을 변경

@WebServlet({"/index.html", "/user/*"})

 

  3) UserController 클래스의 doGet 메소드를 수정

if(command.equals("/index.html")) {
	response.sendRedirect("./");
}
		
//단순 페이지 이동은 포워딩
if(command.equals("/user/main")) {
	RequestDispatcher dispatcher = 
			request.getRequestDispatcher("../member/main.jsp");
	dispatcher.forward(request, response);
}

 

  4) WebContent 디렉토리에 member 디렉토리를 생성하고 main.jsp 파일을 생성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	시작
</body>
</html>

 

  5) web.xml 파일에서 welcome-file-list 수정 : index.jsp를 제외하고 모두 삭제

<welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

 

  6) 프로젝트를 실행해서 main.jsp 파일이 출력되는 지 확인

 

7. 회원 가입 - 데이터 삽입

    => 어떤 항목을 입력받을 것인가? email, passward, nickname, image

    => 입력받아야 하는 항목중에서 유일무이한 값이 있는지 확인 : email, nickname

        - 유일무이하게 저장되어야 하는 값은 중복 검사를 해야 함

    => 입력받는 값 중에서 값에 제한이 있는 항목은 있는가?

        - 값에 제한이 있으면 radio, checkbox, select를 이용해야 함

        - 필수 항목중 하나만 선택 가능 : radio, select

        - 다중 선택 : checkbox, select

        - 필수 선택일 경우 기본값을 선택해주는 것이 좋음

    => 입력하여 전송시는 유효성 검사를 해주어야 함

    => 하나의 서버로 웹 브라우저, 모바일 앱, PC앱을 전부 처리하려면 웹 브라우저에서 폼 데이터 전송시 ajax 이용

  1) 회원가입 요청을 생성 - main.jsp 파일에 생성

<!-- 바로 user/main으로 포워딩하도록 해주는 코드-->
<!-- jsp 파일에서 서버에 요청하는 경우는 상대 경로를 이용하지 않는 것이 좋음
css, js의 링크 그리고 이미지나 동영상 및 사운드의 링크도 동일한 방법으로 설정-->

<a href="${pageContext.request.contextPath}/user/register">회원가입</a>

 

  2) UserController 클래스에서 /user/register 요청이 GET방식으로 오면 처리하는 코드를 doGet 메소드에 추가

else if(command.equals("/user/register")) {
	RequestDispatcher dispatcher = 
		request.getRequestDispatcher("../member/register.jsp");
		dispatcher.forward(request, response);
}

 

  3)  member 디렉토리에 register.jsp 파일을 만들고 작성

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입</title>
</head>
<body>
	<!-- ajax로 요청하는 경우에는 method가 필요없음 
	id 만 설정 - 웹 만 할 때는 method를 post로 설정
	입력하는 폼에 파일이 존재하는 경우는 enctype을 설정해야 합니다.
	설정안하면 파일 업로드 안됨-->
	<form id="registerform" enctype="multipart/form-data">
		<div id="msg">유효성 검사 내용 출력</div>
		이메일<input type="text" name="email" id="email"/>
		<div id="emailmsg"></div>
		비밀번호<input type="password" 
			name="password" id="password"/>
		<div id="passwordmsg"></div>
		비밀번호 확인<input type="password" 
			name="password1" id="password1"/><br/>
		별명<input type="text" 
			name="nickname" id="nickname"/>
		<div id="nicknamemsg"></div>
		이미지<input type="file"
		name="image" id="image" /><br />
		<!-- 웹 만 한다면 type은 submit -->
		<input type="button" value="회원가입" 
			id="registerbtn"/>
		<input type="button" value="메인으로" 
			id="mainbtn"/>	
		<input type="button" value="로그인" 
			id="loginbtn"/>
	</form>
</body>
</html>

 

 

  4) UserDao 클래스에 email 중복검사와 nickname중복검사를 수행해줄 메소드를 생성

    => return 타ㅣ입을 String, int, boolean으로 해도 됨

        - email이나 nickname의 존재여부를 알려줄수 있어야 함

    => 존재하면 false 그렇지 않으면 true를 리턴하도록 작성

public boolean emailCheck(String email) {
	boolean result = false;
	connect();
	
	try {
		//sql 생성
		//email을 대문자로 변경해서 비교
		pstmt = con.prepareStatement(
			"select email from user where upper(email) = ?");
		//데이터 바인딩
		pstmt.setString(1, email.toUpperCase());
		//SQL 실행
		rs = pstmt.executeQuery();
		if(rs.next()) {
			result = false;
		}else {
			result = true;
		}
		
	}catch(Exception e) {
		System.out.println("DAO:" + e.getMessage());
		e.printStackTrace();
	}
	close();
	return result;
}

//nickname 중복 검사를 위한 메소드
public boolean nicknameCheck(String nickname) {
	boolean result = false;
	connect();
	
	try {
		//sql 생성
		pstmt = con.prepareStatement(
			"select nickname from user where upper(nickname) = ?");
		//데이터 바인딩
		pstmt.setString(1, nickname.toUpperCase());
		//SQL 실행
		rs = pstmt.executeQuery();
		if(rs.next()) {
			result = false;
		}else {
			result = true;
		}
		
	}catch(Exception e) {
		System.out.println("DAO:" + e.getMessage());
		e.printStackTrace();
	}
	close();
	return result;
}

 

  5) Dao의 2개 메소드 테스트

    => 프로젝트를 선택하고 마우스 오른쪽을 클릭하고 [Build - Path] - [Configure Build Path]를 선택

        - [Libraries] 탭을 선택 -> [Add Library]를 선택하고 보이는 창에 JUnit을 선택한 후 버전을 선택하고 apply

    => index.jsp 파일에 아래 코드를 작성하고 수정하면서 테스트

<%
	dao.UserDao userDao = dao.UserDao.sharedInstance();
	System.out.println(
			userDao.nicknameCheck("군계"));
	
%>

 

 

  6) Service 인터페이스에 이메일과 닉네임 중복체크를 위한 메소드를 선언

    => Service가 하는 일은 클라이언트가 전달해준 정보를 받아서 처리를 한 후 저장을 해주는 것

    => 이 때 클라이언트의 정보는 HttpServletRequest 클래스의 인스턴스에 있고, 응답할때는 HttpServletResponse 클래스의 인스턴스를 이용

    => 기본모양을 통일

        - public void 메소드명(HttpHttpServletRequest request, HttpServletResponse response)

    => 메소드 2개 선언

public interface UserService {
	public void emailCheck(HttpServletRequest request, HttpServletResponse response);
	public void nicknameCheck(HttpServletRequest request, HttpServletResponse response);
}

 

  7) ServiceImpl 클래스에 이메일과 닉네임 

@Override
public void emailCheck(HttpServletRequest request, HttpServletResponse response) {
	//1.파라미터 읽기
	try {
		request.setCharacterEncoding("utf-8");
	}catch(Exception e) {
		System.out.println("Service:" + e.getMessage());
		e.printStackTrace();
	}
	String email = request.getParameter("email");
	
	//2.별도의 작업을 수행해야 하면 처리
	//암호화, 파일 업로드, 파라미터를 다른 자료형으로 변환
	//업무처리에 필요한 알고리즘
		
	//3.DAO 작업이 필요하면 호출할 DAO 메소드의 매개변수를 생성
		
	//4.DAO의 메소드를 호출해서 결과를 변수에 저장
	boolean result = userDao.emailCheck(email);
		
	//5.단순 웹 페이지를 위한 서버의 경우는 결과들을 request 나 session에 저장
	
	//REST API 서버의 경우는 JSONObject 클래스의 객체를
	//만들어서 저장한 후 request에 저장합니다.
	//웹 페이지를 위한 로그인의 경우에만 session에 저장하던지
	//데이터베이스에 로그인 여부를 저장해 놓습니다.
	JSONObject object = new JSONObject();
	object.put("result", result);
		
	//request에 저장
	request.setAttribute("result", object);
}

@Override
public void nicknameCheck(HttpServletRequest request, HttpServletResponse response) {
	//1.파라미터 읽기
	try {
		request.setCharacterEncoding("utf-8");
	}catch(Exception e) {
		System.out.println("Service:" + e.getMessage());
		e.printStackTrace();
	}
	String nickname = request.getParameter("nickname");
	
	//2.별도의 작업을 수행해야 하면 처리
	//암호화, 파일 업로드, 파라미터를 다른 자료형으로 변환
	//업무처리에 필요한 알고리즘
		
	//3.DAO 작업이 필요하면 호출할 DAO 메소드의 매개변수를 생성
		
	//4.DAO의 메소드를 호출해서 결과를 변수에 저장
	boolean result = userDao.nicknameCheck(nickname);
		
	JSONObject object = new JSONObject();
	object.put("result", result);
		
	//request에 저장
	request.setAttribute("result", object);
		
	//기억해야 할 것은 파라미터 이름(nickname)
	//결과를 저장할 때 사용한 속성이름과 데이터
	//request 나 session에 저장한 이름
		
}

 

  8) UserController 클래스에 email 중복체크와 nickname 중복체크를 처리할 코드를 doGet메소드에 추가

else if(command.equals("/user/emailcheck")){
	RequestDispatcher dispatcher = 
			request.getRequestDispatcher("../member/emailcheck.jsp");
	dispatcher.forward(request, response);
}else if(command.equals("/user/nicknamecheck")){
	RequestDispatcher dispatcher = 
			request.getRequestDispatcher("../member/nicknamecheck.jsp");
	dispatcher.forward(request, response);
}

 

  9) member 디렉토리에 emailcheck.jsp 파일을 만들고 결과를 출력

<%@ page language="java" contentType="text/json; charset=UTF-8"
    pageEncoding="UTF-8"%>
${result}

 

  10) member 디렉토리에 nickname.jsp 파일을 만들고 결과를 출력

<%@ page language="java" contentType="text/json; charset=UTF-8"
    pageEncoding="UTF-8"%>
${result}

 

  11) REST API 서버를 만들경우 테스트

    => 브라우저에 서버주소 뒤에 요청 URL?파라미터이름=파라미터값&파라미터이름=파라미터값..

         http://localhost:8081/user/user/emailcheck?email=gs90826@naver.com결과가 출력되지 않아 디버깅

    => 요청이 Controller까지 도달하는지 확인

        - doGet 메소드의 요청을 처리하는 부분에 콘솔에 출력하는 코드를 추가

        - System.out.println("요청도달");  //이 메시지가 보이면 요청에는 문제가 없음(안보이면 URL 확인)

    => 요청이 service 메소드까지 도달하는지 확인

        - Service의 메소드에 콘솔에 출력하는 코드를 추가

        - 콘솔에 이 메시지가 안보이면 Controller에서 Service의 메소드를 호출하지 못한 것

    => 요청이 DAO 메소드까지 도달하는지 확인

        - DAO의 메소드에 콘솔에 출력하는 코드를 추가

        - 파라미터를 출력하는 코드를 출력

        - 콘솔에 메시지가 안보이면 Service에서 DAO의 메소드를 호출하지 않은 것

        - 메시지가 null로 보일시 파라미터 전달이 잘못된 것으로 service의 파라미터를 읽는 부분과 만드는 부분을 확인

    => DAO 메소드에서 결과를 출력

        - 결과가 출력되지 않으면 예외가 발생했을 가능성이 높음(SQL 오류나 ?에 잘못된 값을 바인딩)

    => 결과가 출력되었다면 그 결과를 제대로 return 하고 있는지 확인

    => ServiceImpl의 메소드에서 DAO의 결과를 저장한 변수를 확인하고 그 값을 JSONObject에 삽입했는지 그리고 JSONObject를 request에 무엇이라고 저장했는지 확인

    => 출력하는 jsp파일에서 확인

 

  12) 위의 내용을 웹에서 사용할 때 수행할 내용을 register.jsp파일에 작성

    => 이경우 전부 자바스크립트로 작업

    => 외부에 자바스크립트 파일을 만들어서 사용하려고 register.jsp파일에 자바스크립트 링크를 추가

    => js/register.js의 경로의 링크 설정

        - <script src="자바스크립트 파일 경로"></script>

  13) member 디렉토리 안에 js 디렉토리를 만들고 그 안에 register.js 파일을 생성하고 작성

 

//스크립트 링크가 body 위에 있다면 window의 load 이벤트 안에 작성
window.addEventListener('load', function(event){
	//id를 가지고 필요한 객체들을 찾아오기
	var registerform = document.getElementById("registerform");
	var msg = document.getElementById("msg");
	var email = document.getElementById("email");
	var emailmsg = document.getElementById("emailmsg");
	
	var password = document.getElementById("password");
	var password1 = document.getElementById("password1");
	var passwordmsg = document.getElementById("passwordmsg");
	
	var nickname = document.getElementById("nickname");
	var nicknamemsg = document.getElementById("nicknamemsg");
	
	var image = document.getElementById("image");
	var registerbtn = document.getElementById("registerbtn");
	var mainbtn = document.getElementById("mainbtn");
	var loginbtn = document.getElementById("loginbtn");
	
	//mainbtn을 클릭하면 메인 화면으로 이동
	mainbtn.addEventListener("click", function(event){
		location.href = "../";
	});
	
	//loginbtn을 클릭하면 login 으로 이동하도록 작성
	loginbtn.addEventListener("click", function(event){
		location.href = "login";
	});
	
	//email 중복검사 통과 여부를 저장할 변수
	var emailcheck = false;
	
	//email을 작성하고 포커스가 떠나면 중복검사를 수행
	email.addEventListener("focusout", function(event){
		if(email.value.trim().length == 0){
			emailmsg.innerHTML = "이메일을 입력하고 넘어가세요!!";
			emailmsg.style.color = 'red';
			//검사하지 않도록 리턴
			return;
		}
		//ajax 요청 객체를 생성
		var request = new XMLHttpRequest();
		//요청 생성
		request.open('get', 
			'emailcheck' + '?' + 'email=' + email.value, true);
		//요청을 전송
		request.send('');
		//결과를 받기 위한 부분 생성
		request.addEventListener('load', function(event){
			//결과를 파싱
			var data = JSON.parse(event.target.responseText);
			if(data.result == true){
				emailmsg.innerHTML = "사용 가능한 이메일";
				emailmsg.style.color = "blue";
				//email 중복 검사를 통과했다고 표시
				emailcheck = true;
			}else{
				emailmsg.innerHTML = "사용 중 인 이메일";
				emailmsg.style.color = "red";
				//email 중복 검사를 통과 못했다고 표시
				emailcheck = false;
			}
		})
	});
	
	//닉네임 중복 검사 통과여부를 저장할 변수
	var nicknamecheck = false;
	
	//닉네임을 작성하고 포커스를 옮길 때
	nickname.addEventListener(
			"focusout", function(event){
		//입력한 내용이 없을 때는 검사하지 않음
		if(nickname.value.trim().length < 1){
			nicknamemsg.innerHTML = "닉네임을 입력하세요";
			nicknamemsg.style.color = 'red';
			nicknamecheck = false;
			return;
		}
		
		var request = new XMLHttpRequest();
		request.open('get',
			'nicknamecheck?nickname='+nickname.value,
			true);
		request.send('');
		//데이터를 가져왔을 때 호출될 메소드를 설정
		request.addEventListener(
			'load', function(event){
			var data = 
				JSON.parse(event.target.responseText);
			if(data.result == true){
				//메시지 출력
				nicknamemsg.innerHTML = '사용 가능한 별명';
				nicknamemsg.style.color = 'green';
				//표시
				nicknamecheck = true;
				
			}else{
				//메시지 출력
				nicknamemsg.innerHTML = '이미 사용 중 인  별명';
				nicknamemsg.style.color = 'red';
				//표시
				nicknamecheck = false;
			}
		});
		
	});
	
});

 

**회원가입

1. UserDao 클래스에 회원가입을 처리하는 메소드를 생성

//데이터 삽입을 위한 메소드
public int register(User user){
	int result = -1;
    connect();
    try{
    	pstmt = con.prepareStatement("insert into user(email, password, nickname, image) values(?, ?, ?, ?)");
    	pstmt.setString(1, user.getEmail());
    	pstmt.setString(2, user.getPassword());
    	pstmt.setString(3, user.getNickname());
    	pstmt.setString(4, user.getImage());
    	
    	// SQL 실행
    	result = pstmt.executeUpdate();
    	System.out.println("register SQL 입력까지 성공");
        
    }catch(Exception e){
    	System.err.println("Register insert 실패!");
    	System.out.println("DAO : " + e.getMessage());
		e.printStackTrace();
    }
    close();
    return result;
}

 

2. UserService 인터페이스에 회원가입 처리를 위한 메소드를 선언

public void register(HttpServletRequest request, HttpServletResponse response);

 

3. UserServiceImpl 클래스에 회원가입처리를 위한 메소드 구현

@Override
public void register(HttpServletRequest request, HttpServletResponse response) {
	//파라미터 읽기
	try {
		request.setCharacterEncoding("utf-8");
		String email = request.getParameter("email");
		String password = request.getParameter("password");
		String nickname = request.getParameter("nickname");
		//파일은 Part로 읽어냄
		Part part = request.getPart("image");
		//파일명 가져오기
		//content-disposition이라는 헤더의 값 이용
		String contentDisposition = part.getHeader("content-disposition");
		//form-data;image;"파일명" 형태의 문자열에서 파일명만 가져오기
		
		//;으로 분리
		String[] splitStr = contentDisposition.split(";");
		//첫번째 "와 마지막"의 위치를 찾음
		int first = splitStr[2].indexOf("\"");
		int last = splitStr[2].lastIndexOf("\"");
		//위치를 가지고 부분 문자열을 가져오기
		String uploadFileName = splitStr[2].substring(first+1, last);
		
		String image = null;
		//위의 파일명에 내용이 없으면 파일을 선택하지 않은 것
		if(uploadFileName != null && uploadFileName.length() !=0) {
			//확장자 추출하기
			String [] imsi = uploadFileName.split("\\.");
			String ext = imsi[imsi.length -1];
			
			//새로운 파일명 만들기
			image = UUID.randomUUID() + "." + ext;
			//파일 업로드
			File f = new File("C:\\Users\\30408\\Desktop\\upload\\" + image);
			part.write(image);
		}
		
		//DAO 파라미터 만들기
		User user = new User();
		user.setEmail(email);
		user.setPassword(password);
		user.setNickname(nickname);
		user.setImage(image);
			
		//Dao 메소드 호출
		int result = userDao.register(user);
		
		//결과를 저장
		JSONObject object = new JSONObject();
		if(result > 0) {
			object.put("result", true);
		}else {
			object.put("result", false);
		}
		
		request.setAttribute("result", object);
		
	}catch (Exception e) {
		System.err.println("인코딩 실패");
		System.out.println("Service : " + e.getMessage());
		e.printStackTrace();
	}
}

 

4. UserController 클래스에 register 요청을 POST 방식으로 전송했을 때 처리할 코드를 doGet메소드에 추가

else if(command.equals("/user/register") && method.equals("GET")){
	RequestDispatcher dispatcher = request.getRequestDispatcher("../member/register.jsp");
	dispatcher.forward(request, response);
}else if(command.equals("/user/register") && method.equals("POST")){
	RequestDispatcher dispatcher = request.getRequestDispatcher("../member/registerresult.jsp");
	dispatcher.forward(request, response);
}

 

5. registerresult.jsp 파일을 만들고 출력하는 코드 작성

<%@ page language="java" contentType="text/json; charset=UTF-8"
    pageEncoding="UTF-8"%>

${result}

 

6. register.jps 파일에서 회원가입 버튼을 누르면 회원 가입 요청하고 그 결과를 가지고 다른 작업을 수행하는 코드 작성

 

 

7. UserController 상단에 파일이 업로드 될 디렉토리를 설정

@MultipartConfig(location="c:\\hybrid\\upload")

 

**비밀번호 암호화

1. 암호화를 위한 라이브러리(jbCrypt)를 WEB-INF/lib 디렉토리에 복사

    => jBCrypt : 복호화가 불가능하도록 해주는 라이브러리

        - Bcrypt.hashpw(문자열, BCrypt.gensalt()) : 메소드를 호출하면 암호화된 문자열을 리턴

        - BCrypt.checkpw(원본 문자열, 암호화된 문자열) : 2개가 일치하면 true, 일치하지 않으면 false를 리턴

 

2. UserServiceImpl 클래스에서 password 저장하는 부분을 변경

user.setPassword(BCrypt.hashpw(password, BCrypt.gensalt()));

 

** 데이터베이스 초기화

    => 회원가입처리를 위해서 샘플데이터를 입력한 상태라서 이전에 입력된 회원정보를 삭제해야 함

    => 이해, 배포 작업시도 동일한 작업을 수행

1. 테이블의 데이터를 삭제하는 방법

    => 테이블 자체를 삭제하고 테이블을 다시 생성

    => truncate table 테이블명; 으로 테이블의 모든 데이터 삭제(데이터가 정말로 삭제됨)

    => delete from 테이블명; 으로 테이블의 모든 데이터 삭제(삭제된것 처럼 보이나 삭제되었다는 표시만 함)

 

**로그인 처리

    => 로그인 요청 -> ID, PW 입력화면으로 이동 -> ID, PW를 입력하고 서버에 로그인 요청 -> ID, PW로 로그인처리

    => DAO에서는 아이디를 가지고 데이터를 찾아오는 작업만 수행

    => Service에서는 아이디를 가지고 찾아온 데이터와 입력한 비밀번호를 비교하여 로그인 여부를 판단

1. main.jsp 파일에 로그인 요청을 위한 링크를 생성

    - <a href="${pageContext.request.contextPath}/user/login">로그인</a>

2. 로그인 페이지 이동 처리를 UserController 클래스의 doGet메소드에 작성

    => WebApplication이 아닌곳에서는 이벤트 핸들러를 이용하여 작성

else if(command.equals("/user/login")){
	userService.nicknameCheck(request, response);
	RequestDispatcher dispatcher = request.getRequestDispatcher("../member/login.jsp");
	dispatcher.forward(request, response);
}

 

3. member 디렉토리에 login.jsp파일을 만들고 화면 디자인

    => WebApplication이 아닌곳에서는 Activitiy나 controller또는 View등에 작성

<title>로그인</title>
<script src="${pageContext.request.contextPath}/member/css/login.css"></script>
<script src="${pageContext.request.contextPath}/member/js/login.js"></script>
</head>
<!-- action :처리할 서버의 URL
method: 전송방식(get, post)
enctype: file이 존재하는 경우만 multipart/form-data
최근에는 조회를 제외하고는 action을 설정하지 않고 method는 post로 설정하여 get과 post로 구분-->
<form method="post" id="loginform">
	아이디:<input type="text" id="id" name="id" /><br/>
	비밀번호 : <input type="password" id="password" name="password" /><br/>
	<!-- 하나의 서버로 여러 디바이스의 요청을 처리하는 REST API서버를 이용하는 경우에는 submit대신에 button을 만들어서 
	버튼을 누르면 ajax로 요청을 처리
	bootstrap 같은 자바스크립트 라이브러리를 이용하게 되면
	button을 자동으로 submit으로 만드는 경우가 있음
	그런경우 버튼을 form 외부에 생성 -->
	<input type="button" value="로그인" id="loginbtn"/>
	<input type="button" value="메인" id="mainbtn"/>
	
</form>

 

4. UserDao 클래스에 로그인 처리를 위한 메소드를 선언

    => User 정보를 가진 테이블의 DTO나 Map을 리턴하도록 하고, 팍라미터는 아이디

    => 아이디를 가지고 User 정보를 리턴하도록 만들어야 함

// 로그인 처리를 위한 메소드
public User login(String email) {
	// 없는 아이디인 경우는 null을 리턴
	User user = null;
	connect();
	try {
		// SQL 만들기
		// user 테이블에서 id를 가지고 데이터를 찾아오기
		pstmt = con.prepareStatement("select * from user where email=?");
		pstmt.setString(1, email);
		// SQL 실행
		rs = pstmt.executeQuery();
		// 데이터 읽어서 저장
		if (rs.next()) {
			user = new User();
			user.setEmail(rs.getString("email"));
			user.setPassword(rs.getString("password"));
			user.setNickname(rs.getString("nickname"));
			user.setImage(rs.getString("image"));
		}
	} catch (Exception e) {
		System.out.println("DAO:" + e.getMessage());
		e.printStackTrace();
	}
	close();
	return user;
}

 

5. Service 인터페이스에 로그인 처리를 위한 메소드를 선언

//로그인을 위한 메소드
public void login(HttpServletRequest request, HttpServletResponse response);

 

6. ServiceImpl 클래스에 로그인 처리를 위한 메소드를 구현

@Override
public void login(HttpServletRequest request, HttpServletResponse response) {
	//1. 파라미터 읽기
	try {
		request.setCharacterEncoding("utf-8");
	}catch (Exception e) {
		System.out.println(e.getMessage());
		e.printStackTrace();
	}
	String email = request.getParameter("email");
	String password = request.getParameter("password");
	
	//2. 필요한 처리
	
	//3. DAO 메소드를 호출해야 하면 DAO 메소드의 매개변수를 생성
	
	//4. DAO 메소드를 호출해서 결과를 저장
	User user = userDao.login(email);
	
	//5. 결과를 가지고 필요한 처리를 수행
	if(user != null) {
		//비밀번호 확인
		if(BCrypt.checkpw(password, user.getPassword())) {
			//비밀번호가 일치하는 경우 - 비밀번호는 삭제
			user.setPassword(null);
		}else {
			//비밀번호가 틀린경우 user == null
			user = null;
		}
	}
	JSONObject object = new JSONObject();
	if(user != null) {
		object.put("email", user.getEmail());
		object.put("nickname", user.getNickname());
		object.put("image", user.getImage());
	}else {
		object = null;
	}
	
	//6. 요청 처리 결과를 저장
	request.getSession().setAttribute("result", object);
}

 

7. UserController 클래스의 doGet  메소드에 login 요청이 POST로 온경우 처리를 위한 코드를 작성

else if(command.equals("/user/login") && method.equals("POST")){
	//로그인 처리를 위한 서비스 메소드 호출
	//작업을 처리하는 경우 서비스의 메소드를 호출하는 것이고, 단순 페이지 이동은 서비스의 메소드를 호출하지 않음
	//정보수정의 경우 상세데이터를 가져오고 수정
	userService.login(request, response);
	RequestDispatcher dispatcher = request.getRequestDispatcher("../member/loginresult.jsp");
	dispatcher.forward(request, response);
}

 

8. member 디렉토리에 loginresult.jsp 파일을 생성하여 결과를 출력

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
${result}

 

9. login.jsp 파일에 요청을 하고 결과를 처리하는 부분을 작성

    => ajax로 처리할 때는 각 요소에 id를 설정하고 javascript에서 처리

 

10. main.jsp 수정 - 로그인된 경우와 그렇지 않은 경우 구분

<a href="${pageContext.request.contextPath}/user/register">회원가입</a>
<c:if test="${result == null }">
	<a href="${pageContext.request.contextPath }/user/login">로그인</a>
</c:if>
<c:if test="${result != null }">
	<a href="${pageContext.request.contextPath }/user/logout">로그아웃</a>
</c:if>

 

11. 주의할 점

    => 웹 페이지에서 계속 사용해야 하는 정보는 request에 저장하면 안되고 session에 저장해야 함

    => session에 저장하는 것은 로그인 정보

 

12. 웹에서의 로그아웃 처리

    => session을 invalidate() 시키거나 로그인 정보를 저장하고 있는 항목을 삭제

    => request.getSession().invalidate();  또는 request.getSession().removeAttribute("result");

 

 

 

 

 

**Domain

    => Domain : 일반 프로그래밍에서는 업무에 대한 지식

    => Domain : Web에서는 하나의 Web Application을 Domain이라고도 함

       (이때의 도메인은 Domain Name Service에 등록할 때 사용하는 하나의 URL 개념)

 

**ajax

    => 도메인 내의 데이터를 비동기적으로 가져오는 기술

    => 도메인 외부의 데이터는 근본적으로 ajax를 이용해서 가져올수 없음

        - 동일 출처 정책

        - same-origin policy 라고 하는데 보안 목적 때문에 외부에서 요청한 데이터를 제공하지 않음

    => 예외로 서버측에서 외부에도 데이터를 제공하겠다고 설정하면 데이터를 ajax를 이용해서 가져올 수 있음

        - 이러한 서비스를 jsonp 라고 함

        - CORS(Cross-Origin Resource Sharing)이라고도 함

    => OpenAPI 중에는 ajax로는 가져올수 없고, 서버용 언어의 코드로는 가져올수 있는 데이터가 있음

        - 이 경우ajax로 처리하고자 하면, ajax에서 Controller로 내부 요청을 전송

        - Controller는 그 내부 요청을 가지고 Service에서 데이터를 가져와서 결과 페이지로 출력

        - 이러한 방식을 proxy라고 함

        - proxy는 내부에서 요청한 외부 데이터를 대신 가져다 주는 기술

        - 보안 목적 때문에 일부러 이렇게 구현하기도 함

        - 중요 데이터를 다룰 경우 외부 요청시, 직접 가지 못하게 하고, Proxy server를 경유하여 위험장소의 요청은 거절

1. 기상청에서 날씨 정보를 넘겨주는 API

    => http://www.kma.go.kr/weather/forecast/mid-term-xml.jsp?stnld=109

 

2. main.jsp 파일에 데이터를 ajax로 요청 : 동일 출처 정책에 의해서 안됨 - CORS

<script>
	//ajax 요청 코드
	var request = new XMLHttpRequest();

	//ajax 요청 생성
	request.open('get', 'http://www.kma.go.kr/weather/forecast/mid-term-xml.jsp?stnld=109', true);

	//요청을 전송
	request.send();

	//요청을 전송하고 응답이 오면 호출될 콜백 메소드 생성
	request.addEventListener('load', function(event){
		alert(event.target.responseXML);
	});
</script>

 

3. Service 인터페이스에 Proxy 요청을 처리해 줄 메소드를 1개 생성

//proxy 서비스를 위한 메소드
public void proxy(HttpServletRequest request, HttpServletResponse response);

 

4. ServiceImpl 메소드에 Proxy 요청을 처리해 줄 메소드를 구현

@Override
public void proxy(HttpServletRequest request, HttpServletResponse response) {
	//Java Application에 구현할 때는 Thread 안에 구현
	//Android Application에 구현할 때는 Thread 안에 구현하고
	//화면에 표시할 때는 Handler를 이용하거나 Thread 와 Handler의 조합인 AsyncTask를 이용
	
	try {
		//데이터를 가져올 URL을 생성
		URL url = new URL(
        "http://www.kma.go.kr/weather/forecast/mid-term-xml.jsp?stnld=109");
		//연결 객체를 생성
		HttpURLConnection conn = (HttpURLConnection)url.openConnection();
		//옵션을 설정
		conn.setConnectTimeout(30000);
		conn.setUseCaches(false);
		//헤더 설정을 해야 하는 경우에는
		//conn.setRequestProperty("헤더이름", "헤더값");
		
		//post 전송이면
		//conn.setRequestMethod("POST");
		
		//데이터 읽어오기
		StringBuilder sb = new StringBuilder();
		BufferedReader br = 
			new BufferedReader(
				new InputStreamReader(
					conn.getInputStream()));
		while(true) {
			String line = br.readLine();
			if(line == null) break;
			sb.append(line + "\n");
		}
		
		br.close();
		conn.disconnect();
		request.setAttribute("result", sb.toString());
		
	}catch(Exception e) {
		System.out.println(e.getMessage());
		e.printStackTrace();
	}
}

 

5. UserController 클래스에 요청을 처리하는 코드를 추가

else if(command.equals("/user/proxy") && method.equals("GET")){
	userService.proxy(request, response);
	RequestDispatcher dispatcher = request.getRequestDispatcher("../member/proxy.jsp");
	dispatcher.forward(request, response);
}

 

6. member 디렉토리에 proxy.jsp 파일을 만들고 결과를 출력

<%@ page language="java" contentType="text/xml; charset=UTF-8"
    pageEncoding="UTF-8"%>
${result}

 

7. main.jsp 파일의 ajax요청 경로 수정

<script>
	//ajax 요청 객체 생성
	var request = new XMLHttpRequest();
	//ajax 요청 생성
	request.open('get', 'user/proxy', true);
	//요청을 전송
	request.send('');
	//요청을 전송하고 응답이 오면 호출될 콜백 메소드 생성
	request.addEventListener('load', function(event){
		alert(event.target.responseText)
	});
</script>

 

8. 실제 서비스라면 ServiceImpl 클래스에서 데이터를 parsing 하여 출력하고자하는 결과를 만들어서 제공

 

 

Tip!

** 템플릿 메소드 패턴 : 인터페이스 -> 클래스

    => C, C++, Objective-C : header(.h, .hpp등) -> 구현체(c, cpp, m, mm)

 

**URL 패턴

    => 웹 서버를 만들 때 중요

    => /*  : 모든 요청을 처리

         /디렉토리/* : 디렉토리로 시작하는 요청을 전부 처리

         *.확장자 : 확장자로 끝나는 요청을 전부 처리

         /경로 : 경로에 해당하는 요청만 처리

    => 디렉토리 패턴과 확장자 패턴은 같이 사용할 수가 없음

        - /디렉토리/*.확장자 : 에러

    => spring에서는 /패턴이 추가되는 데, /는 .jsp를 제외한 모든 요청을 처리

 

**Web에서 비동기 처리 - ajax

    => javascript만 이용

        - var 변수명(request) = new XMLHttpRequest();

        - 변수명.open("전송방식", url, 비동기여부); //특별한 경우가 아니면 비동기로 처리

        - 변수명.send(파라미터); //요청을 전송

            -> 전송방식이 post일 때는 send에 파라미터를 보냄

            -> get방식일 경우 url뒤에 ?하고 파라미터이름=값&파라미터이름=값 형태로 전송

        //요청에 응답했을 때 호출되는 콜백 메소드 등록

        - 변수명.addEventListener('load', function(event)){

                   //e.target.responseText가 서벙가 응답한 결과 - json

                   //e.target.responseXML이 XML로 응답한 결과

                   //응답이 오면 그 결과를 파싱하여 사용

                   //json parsing을 할 때는 JSON.parse(문자열)

          });

    => 편리하게 작업하고자 하는 경우에는 javascript 라이브러리 - jquery

 

**BackEnd 개발자가 자바스크립트 학습

    => HTML에 만든 태그를 자바스크립트 객체로 변환하는 방법

    => 이벤트 처리

    => ajax

 

**FrontEnd 개발자는 자바스크립트를 문법부터 충실하게 공부해야 함

    => WebFrontEnd 개발자는 당연히

    => Mobile FrontEnd 개발자도 hybrid app 개발을 위해서는 자바스크립트를 해야 함

 

**스크립트 언어의 특징

    => 줄단위로 해석하면서 실행하기 때무에 상단에 에러가 있으면 하단의 문장이 실행이 안됨

        - 하단에 에러가 있는 경우는 에러가 발생하기 전까지 실행이 됨

    => 자바스크립트, 파이썬, 루비, 코틀린, 스위프트