빅데이터 Java 개발자 교육 - 52일차[JSP 실습 3(RestAPI 활용 실습)]

Jun_Gyu·2023년 4월 12일
0
post-thumbnail

h2 DB 오류 개선

	// 정적 객체 생성
	private static SqlSession sqlSession;

	public static SqlSession getSqlSession() {
		return sqlSession;
	}

	// 최초 한번만 생성됨.
	static {
		try {
			// h2 테스트용 DB 접속
			BasicDataSource dataSource = new BasicDataSource();
			dataSource.setDriverClassName("org.h2.Driver");
			dataSource.setUrl("jdbc:h2:tcp://1.234.5.158:31521/ds209;Mode=Oracle");
			dataSource.setUsername("sa");
			dataSource.setPassword("");

			// 자동 COMMIT 설정
			TransactionFactory transactionFactory = new JdbcTransactionFactory();
			Environment environment = new Environment("development", transactionFactory, dataSource);
			Configuration config = new Configuration(environment);

			// 오라클 내의 컬럼명에서 "_" 사용 가능
			config.setMapUnderscoreToCamelCase(true);

			// 만든 매퍼 등록
			config.addMapper(BoardMapper.class);
			config.addMapper(ItemMapper.class);
			config.addMapper(ItemImageMapper.class);
			config.addMapper(MemberMapper.class);

			SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(config);
           // 기존에는 return = factory.openSession(true);이었으나, 정적 객체로 전송
			sqlSession = factory.openSession(true); // true면 자동으로 commit 수행

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

	}

}

어제 실습도중 트래픽 증가로 인해서 서버가 자주 다운됐었다. 이를 해결하기 위해서 sqlSession 객체를 정적으로 최초 한번만 생성되도록 위와 같이 코드를 수정하였다.

유효성 검사 주의점

회원가입 페이지에서 다른 대부분의 항목들은 유효성 검사를 통과했지만, 마지막 '나이'를 설정하는 부분에서 유효성 검사를 무시하고 페이지가 전환되는 문제가 발생하였다.

이는 회원가입을 완료하는 버튼이 submit으로 설정되어있기 때문이었다.

위와 같이 submit으로 input타입을 지정하게 되면 강제로 데이터를 먼저 전송하려고 하기 때문에 유효성 검사가 무시되는 현상이 발생하게 되는 것이다.

이를 해결하기 위해서는 input의 type을 submit이 아닌 다른타입으로 수정하고,
<script>문에서 유효성 검사를 실행하는 맨 마지막 밑부분에 아래와 같이 submit 명령어를 추가해야 하겠다.

// form의 내용을 button을 눌러 강제로 submit 수행!
	document.getElementById('form').submit();

getElementById의 값이 'form'인 이유는 회우너 정보 입력칸 모두를 id가"form"인 <form>태그로 감싸줬기 때문이다.

로그인 세션

지난 실습때도 사용했었던 방법으로, 세션에 사용자의 정보를 기록하여 사용자의 개인별 정보를 제공하는 용도로 사용된다. 보안을 유지하기 위해 로그인 후 세션에 기본적으로 30분동안 기록이 유지되며, 로그아웃시 모든 기록된 정보를 지우는 방식이다.

controller

package customerController;

import java.io.IOException;

import config.Hash;
import config.MyBatisContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import webdto.Member;
import webmapper.MemberMapper;

// 컨트롤러 역활 X, DB에 있는 이미지를 URL 형태로 변경해서 반환하는 용도
@WebServlet(urlPatterns = { "/customer/login.do" })
@MultipartConfig()
public class CustomerLogin extends HttpServlet {
	private static final long serialVersionUID = 1L;

// ex) http://127.0.0.1:8080/web01/customer/login.do
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		request.getRequestDispatcher("/WEB-INF/customer/login.jsp").forward(request, response);

	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 비밀번호 암호화
		String hashPw = Hash.hashPW(request.getParameter("id"),
									request.getParameter("pw"));
		Member obj = new Member();
		obj.setId(request.getParameter("id"));
		obj.setPassword(hashPw);

		// mapper를 이용하여 로그인
		Member ret = MyBatisContext.getSqlSession()
				.getMapper(MemberMapper.class).selectmemberLogin(obj);
		
		if (ret != null) { // 로그인 성공시
			// 세션에 기록 또는 읽기 위한 객체 생성
			HttpSession httpSession = request.getSession();
			// 세션에 필요한 정보를 기록 
			// ex) 아이디, 이름을 기록 (30분간 유지)
			httpSession.setAttribute("UID", ret.getId());
			httpSession.setAttribute("UNAME", ret.getName());
			httpSession.setAttribute("UROLE", ret.getRole());
			
			request.setAttribute("url", "home.do");
			request.setAttribute("icon", "success");
			request.setAttribute("title", "로그인 성공!");
			request.setAttribute("text", "환영합니다! "+ ret.getName() +"님!");
			request.getRequestDispatcher("/WEB-INF/alert.jsp").forward(request, response);
		} else { // 로그인 실패시
			request.setAttribute("url", "login.do");
			request.setAttribute("icon", "error");
			request.setAttribute("title", "로그인 실패");
			request.setAttribute("text", "아이디 혹은 비밀번호가 일치하지 않아요!");
			request.getRequestDispatcher("/WEB-INF/alert.jsp").forward(request, response);
		}
		
	}
}

홈 화면

위의 사진이 로그인을 했을때의 모습이고,

아래의 사진은 로그인 정보가 없을때의 모습이다. 이렇게 현재 로그인의 여부에 따라서 다르게 적용 될 수 있도록 아래와 같이 JSP를 구성해주었다.

home.jsp

<div class="container">
		<a href="home.do" class="btn btn-primary">홈으로</a>
       
		<!-- 세션이 빈 상태일때  -->
		<c:if test="${sessionScope.UID eq null}">
			<a href="login.do" class="btn btn-success">로그인</a>
			<a href="join.do" class="btn btn-success">회원가입</a>
		</c:if>
       
		<!-- 세션에 로그인 정보가 들어있을때  -->
		<c:if test="${sessionScope.UID ne null}">
			<a href="${pageContext.request.contextPath}/board/select.do"class="btn btn-success">게시판조회</a>
			<a href="${pageContext.request.contextPath}/item/select.do"class="btn btn-success">물품조회</a>
			<a href="#" onclick="logoutAction()" class="btn btn-danger">로그아웃</a>
		</c:if>
		<hr />
</div>

로그인 방식

form 방식

JSP

<div class="container">
		<div style="width: 600px; margin: 0 auto; padding: 50px; border: 1px solid #efefef;">
			<h3>로그인</h3>
			<form action="login.do"></form>
			<!-- /web01/customer/'여기만 수정됨.' -->
			<form action="/login.do"></form>
			<!-- /login.do -->

			<form action="${pageContext.request.contextPath}/customer/login.do"
				method="post" id="form">
				<div class="row">
					<div class="col-sm">
						<div class="form-floating mb-2">
							<input type="text" id="id" name="id" class="form-control" /> <label
								for="id" class="form-label">아이디</label>
						</div>

						<div class="form-floating mb-2">
							<input type="password" id="pw" name="pw" class="form-control" />
							<label for="pw" class="form-label">암호</label>
						</div>
						<div>
							<!-- 여기서 type을 submit으로 잡고 유효성 검사를 하게되면 강제로 데이터가 넘어가기 때문에 button으로 지정해주어야 함! -->
							<input type="button" value="로그인" class="btn btn-success"
								onclick="loginAction()" /> 
							<a href="join.do" class="btn btn-secondary">처음이신가요?</a> 
							<a href="home.do" class="btn btn-primary">홈으로</a>
						</div>
					</div>
				</div>
			</form>
		</div>

JS

function loginAction() {
		 const id = document.getElementById("id");
		 const pw = document.getElementById("pw");
		 
		 if(id.value.length <=0){
				Swal.fire({
					icon: 'error',
					title: '아이디가 비어있어요.',
					text: '아이디를 다시 확인해주세요!',
					showConfirmButton: true,
					timer: 2500
				});
				id1.focus();
				return false; // 함수종료, 전송하지 않음
			}
		 if(pw.value.length <=0){
				Swal.fire({
					icon: 'error',
					title: '패스워드가 비어있어요.',
					text: '패스워드를 다시 확인해주세요!',
					showConfirmButton: true,
					timer: 2500
				});
				pw1.focus();
				return false; // 함수종료, 전송하지 않음
			}
		 
		 document.getElementById("form").submit();
	 }

restapi 방식 (ajax)

RestAPI 방식은 기존의 form 방식과는 달리 창을 따로 변경하지 않고 프론트 페이지는 그대로 유지한채, 데이터만 전송하도록 하는 방식이다.

JSP

<div style="width:600px; margin:0 auto; padding: 50px; border:1px solid #efefef;">
       	<h3>로그인(ajax)</h3>
           <div class="row">
               <div class="col-sm">
	                <div class="form-floating mb-2">
	                    <input type="text" id="id1" class="form-control" />
	                    <label for="id1" class="form-label">아이디</label>
	                </div>
	                <div class="form-floating mb-2">
	                    <input type="password" id="pw1" class="form-control" />
	                    <label for="pw1" class="form-label">암호</label>
	                </div>
	                <div>
                   	<input type="button" value="로그인" class="btn btn-success" 
                   		onclick="loginAction1()"/>
                   	<a href="join.do" class="btn btn-secondary">처음이신가요?</a> 
						<a href="home.do" class="btn btn-primary">홈으로</a>	
                   </div>
               </div>
           </div>
		</div>
	</div>

JS

async function loginAction1(){
		const id = document.getElementById("id1");
		const pw = document.getElementById("pw1");
		
		const url 	   = '${pageContext.request.contextPath}/api/member/login.json';
		const headers  = { "Content-Type": "application/x-www-form-urlencoded" };
		const body 	   = { id : id.value, pw : pw.value };
		const { data } = await axios.post(url, body, {headers});
		console.log(data);
		if(data.ret === 1) {
			Swal.fire({
				icon: 'success',
				title: '로그인 성공!',
				text: '${sessionScope.UNAME}님 안녕하세요?',
				showConfirmButton: true,
				timer: 3000
			});
		
			window.location.href='home.do';
		}
		else{
			Swal.fire({
				icon: 'error',
				title: '로그인 오류!',
				text: '아이디 또는 패스워드를 다시 확인해주세요!',
				showConfirmButton: true,
			});
		}
	}

url은 따로 생성한 api용 controller인 api/member/login.json으로 지정해주으며,
body 부분에는 전송하고자 하는 데이터값의 value들을 각각 지정해 담아주었다.

이러한 내용들을 담은 data를 바탕으로 유효성 검사 등을 if문을 통해서 실행할 수 있으며,
유효성 검사를 통과하면 window.location.href='보낼 주소명'; 코드를 통해 원하는 방향으로 페이지를 유도할 수 있겠다.

Controller

package restcontroller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import com.google.gson.Gson;

import config.Hash;
import config.MyBatisContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import webdto.Member;
import webmapper.MemberMapper;

@WebServlet(urlPatterns = { "/api/member/login.json" })
public class RestMemberLoginController extends HttpServlet {
	private static final long serialVersionUID = 1L;
   private Gson gson = new Gson();
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String hashPw = Hash.hashPW( request.getParameter("id"),request.getParameter("pw"));
		
		Member obj = new Member();
		obj.setId( request.getParameter("id") );
		obj.setPassword( hashPw );
		System.out.println(obj.toString());
		
		Member ret = MyBatisContext.getSqlSession().getMapper(MemberMapper.class)
				.selectmemberLogin(obj);
		
		response.setContentType("application/json");
		Map<String, Object> map = new HashMap<>();
		map.put("ret", 0); // 실패시
		if(ret != null) {
			// react.js, vue.js등의 프런트엔드 프레임워크 개발시 토큰을 발행시킴 
			HttpSession httpSession = request.getSession();
			httpSession.setAttribute("UID", ret.getId());
			httpSession.setAttribute("UROLE", ret.getRole());
			httpSession.setAttribute("UNAME", ret.getName());
			map.put("ret", 1); //성공시
		}
		String jsonString = gson.toJson(map);
		PrintWriter out = response.getWriter();
		out.print(jsonString);
		out.flush();
	}
}

controller의 경우는 위와같이 코드를 구성해주었으며, HttpSession을 통해 로그인 후 세션에 사용자의 정보(아이디, 역할, 이름)가 등록되도록 조건을 부여하였다.

이를 이용하여 이번에는 판매자의 회원가입 페이지를 RestAPI방식으로 구성해보았다.

SellerJoin.jsp

JSP

<div class="container">
		<div style="width: 600px; margin: 0 auto; padding: 50px; border: 1px solid #efefef;">
			<h3>판매자 회원가입</h3>
				<div class="row">
					<div class="col-sm">
						<div class="form-floating mb-2">
							<input type="text" id="id" name="id" class="form-control"
								onkeyup="ajaxIDCheck(this)"> 
								<label for="id" id="lbl_check" class="form-label">사용할아이디</label>
						</div>

						<div class="form-floating mb-2">
							<input type="password" id="pw" name="pw" class="form-control"
								required /> <label for="pw" id="lbl_pwCheck" class="form-label">암호</label>
						</div>
						<div class="form-floating mb-2">
							<input type="password" id="pw1" class="form-control" required /> <label for="pw1"
								id="lbl_pwCheck" class="form-label">암호재확인</label>
						</div>
						<div class="form-floating mb-2">
							<input type="text" id="name" name="name" class="form-control"
								required /> <label for="name" class="form-label">이름</label>
						</div>
						<div class="form-floating mb-2">
							<input type="number" id="age" name="age" class="form-control" /> <label
								for="age" class="form-label">나이</label>
						</div>
						<div>
							<!-- 여기서 type을 submit으로 잡고 유효성 검사를 하게되면 강제로 데이터가 넘어가기 때문에 button으로 지정해주어야 함! -->
							<input type="button" value="회원가입" class="btn btn-success" onclick="joinAction()" />
							<a href="login.do" class="btn btn-secondary" >로그인으로</a>
							<a href="home.do" class="btn btn-primary">홈으로</a>
						</div>
					</div>
				</div>
		</div>
	</div>

JS

async function restJoinAction(){
		const id = document.getElementById('id');
		const pw = document.getElementById('pw');
		const name = document.getElementById('name');
		const age = document.getElementById('age');
		
		const url 	   = '${pageContext.request.contextPath}/api/seller/join.json';
		const headers  = { "Content-Type": "application/x-www-form-urlencoded" };
		const body 	   = { id : id.value, pw : pw.value, name : name.valeue, age : age.value  };
		const { data } = await axios.post(url, body, {headers});
		console.log(data);
		
		if(data.ret === 1) {
			Swal.fire({
				icon: 'success',
				title: '회원가입 성공!',
				text: '새로 가입한 정보로 로그인해주세요!',
				showConfirmButton: true,
				timer: 3000
			});
		
			window.location.href='home.do';
		}
		else{
			Swal.fire({
				icon: 'error',
				title: '회원가입 오류!',
				text: '관리자에게 문의해주세요.',
				showConfirmButton: true,
			});
		}
	}
	
	
	// 공통변수 모든 함수에서 사용가능함.
	
	var idcheck = 0; // 1이면 사용가능, 나머지는 사용불가 처리
	function joinAction() {
		const id = document.getElementById('id');
		const pw = document.getElementById('pw');
		const name = document.getElementById('name');
		const age = document.getElementById('age');
		
		if(id.value.length <=0){
			Swal.fire({
				icon: 'warning',
				title: '아이디를 입력하지 않았어요',
				text: '아이디는 어디로 간거죠..?',
				showConfirmButton: true,
				timer: 2500
			});
			id.focus();
			return false; // 함수종료, 전송하지 않음
		}
		
		if(idcheck === 0) {
			Swal.fire({
				icon: 'warning',
				title: '이미 사용중인 아이디네요!',
				text: '이런.. 이 멋진걸 누군가 이미 선점했나 보네요.',
				showConfirmButton: true,
				
			});
			id.focus();
			return false;
		}
		
		if(pw.value.length <=0){
			Swal.fire({
				icon: 'error',
				title: '패스워드를 입력하지 않았어요',
				text: '패스워드는 어디로 간거죠..?',
				showConfirmButton: true,
			});
			pw.focus();
			return false; // 함수종료, 전송하지 않음
		}
		
		if(pw.value.length <=7){
			Swal.fire({
				icon: 'warning',
				title: '패스워드가 너무 짧아요!',
				text:  '적어도 8자리 정도는 되어야 할 것 같아요.',
				showConfirmButton: true,
				
			});
			pw.focus();
			return false; // 함수종료, 전송하지 않음
		}
		
		if(pw1.value.length <=0){
			Swal.fire({
				icon: 'error',
				title: '패스워드 확인창이 비어있어요!',
				text: '패스워드를 한번 더 입력해주세요.',
				showConfirmButton: true,
				
			});
			pw1.focus();
			return false; // 함수종료, 전송하지 않음
		}
		
		if(pw.value !== pw1.value){
			Swal.fire({
				icon: 'warning',
				title: '패스워드가 일치하지 않아요!',
				text: '흠.. 다시 한번 확인해야할 것 같네요.',
				showConfirmButton: true,
				
			});
			pw1.focus();
			return false;
		}
		
		if(name.value.length <=0){
			Swal.fire({
				icon: 'error',
				title: '이름을 입력하지 않았어요!',
				text: '저희가 뭐라고 불러드리면 좋을까요?',
				showConfirmButton: true,
				
			});
			name.focus();
			return false;
		}
		
		if(age.value.length <=0){
			Swal.fire({
				icon: 'error',
				title: '나이를 입력하지 않았어요!',
				text: '민감한 주제이긴 하죠!',
				showConfirmButton: true,
				
			});
			age.focus();
			return false;
		}
		
		restJoinAction();
	}
	
	
	
	
	async function ajaxIDCheck(e){
		if(e.value.length>0) {
			// rest api 호출
			const url 		= '${pageContext.request.contextPath}/api/member/idcheck.json?id=' + e.value;
			const headers 	= {"Content-Type":"application/json"};
			const {data} 	= await axios.get(url, {headers});
			console.log(data);
			
			if(data.ret === 1){
				idcheck = 0;
				document.getElementById("lbl_check").innerText = '이미 사용중인 ID입니다!';
				document.getElementById("lbl_check").style.color = 'red';
				document.getElementById("id").className = 'form-control is-invalid';
			}
			else if(data.ret === 0){
				idcheck = 1;
				document.getElementById("lbl_check").innerText = '사용가능!';
				document.getElementById("lbl_check").style.color = 'blue';
				document.getElementById("id").className = 'form-control';

			}
		}
		else {
			document.getElementById("lbl_check").innerText = '아이디';
			document.getElementById("lbl_check").style.color = 'black';
			document.getElementById("id").className = 'form-control';
		}
	}

JS의 경우에는 유효성 검사 기능과 RestAPI로 데이터를 전송하는 부분을 분리시켜 구성하였다.

유효성 검사가 모두 끝나는 마지막 부분에 객체명을 삽입하여 실행이 되도록 구성했다.

Controller

package restcontroller;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

import com.google.gson.Gson;

import config.Hash;
import config.MyBatisContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import webdto.Member;
import webmapper.MemberMapper;

//127.00.1:8080/web01/api/seller/join.json
@WebServlet(urlPatterns = { "/api/seller/join.json" })
public class RestSellerJoinController extends HttpServlet {
	private static final long serialVersionUID = 1L;
    private Gson gson = new Gson();
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String hashPw = Hash.hashPW(request.getParameter("id"), request.getParameter("pw"));

		Member obj = new Member();
		obj.setId(request.getParameter("id"));
		obj.setPassword(hashPw);
		obj.setName(request.getParameter("name"));
		obj.setAge(Integer.parseInt(request.getParameter("age")));
		obj.setRole("SELLER");
		
		int ret = MyBatisContext.getSqlSession().getMapper(MemberMapper.class)
				.insertMemberOne(obj);

		Map<String, Object> map = new HashMap<>();
		map.put("ret", 0); // map => {"result":0}
		
		if (ret == 1) {
			map.put("ret", 1); // {"result":1}
		} 
		// 오브젝트타입 json으로 변경 (gson라이브러리)
		String jsonString = gson.toJson(map);
		
		response.setContentType("application/json");
		PrintWriter out = response.getWriter();
		out.print(jsonString);
		out.flush();
	}
}
profile
시작은 미약하지만, 그 끝은 창대하리라

0개의 댓글