-
board,member(spring.ver1)JAVA 2021. 7. 26. 22:30
TDD(Test Driven Development)
“테스트 주도 개발(Test-Driven Development)”이라는 용어 그대로 이해해보면, 개발을 하는 데 있어서 테스트가 주가 되어 개발한다는 의미.
“테스트를 염두에 둔 프로그램 개발 방법”이라고 이해하기 ( 자바-스프링 코드를 테스트로 돌릴 수 있는 도구 )
1. board_ui
>> 게시물컨트롤
@Controller public class 게시물컨트롤{ @Autowired IBoardDAO boardDAO; @GetMapping("board") public String 게시물등록준비하다() { // 요청 // 업무 // 경로 지정 return "게시물등록창"; } // @PostMapping("board") 같은 말 @RequestMapping(value="board", method=RequestMethod.POST) public ModelAndView 게시물등록하다(Board board, HttpSession session) {//세션필요하면 그냥 매개값으로 Spring 이 알아서 준다. // 요청 ModelAndView mv = new ModelAndView(); //다른값들은 알아서 들어가지만 회원의 번호는 session 통해서 가져와 넣어놓고 DB에 save 해야한다. int 로그인한회원번호 = (Integer)session.getAttribute("no"); Member 작성한회원 = new Member(); 작성한회원.setNo(로그인한회원번호); board.setWriter(작성한회원); boardDAO.save(board); // 경로지정 mv.addObject("title", board.getTitle()); mv.setViewName("게시물등록결과통보"); return mv; } }
2. login_ui
>> 로그인아웃컨트롤
@Controller public class 로그인아웃컨트롤{ @Autowired IMemberDAO dao; @GetMapping("login")//로그인준비, 이름이 겹치니까 method 를 다르게 해서 겹치지 않게 한다. get, post public String process() { return "로그인창"; } @PostMapping("faillogin")//로그인준비, 이름이 겹치니까 method 를 다르게 해서 겹치지 않게 한다. get, post public String 실패로그인다시하다() { return "로그인창"; } @PostMapping("login") public ModelAndView process(@RequestParam("id")String id,String password, HttpSession session) {//변수명에 맞춰 getParameter 자동으로 해줌. //만약 들어오는 name 속성값과 변수명이 다르다면 @RequestParam("name값") 을 앞에 매칭시켜준다. //요청 Object[] NoAndName = dao.isin(id, password); ModelAndView mv = new ModelAndView(); if(NoAndName==null) { //여기서 조건을 따져 forward 로 보내며 msg 까지 설정하면 로그인준비 control 에서 다시 조건을 따져 request 로 값을 받고 view 넘기지 않아도 //이 값들이 request에 누적되어 view까지 넘어간다. mv.addObject("message", "아이디 또는 비밀번호가 일치하지 않습니다."); mv.setViewName("forward:/faillogin");//여긴 redirect 하면 request.setAttribute() 가 안가니까 forward //애초에 faillogin 요청명으로 주소창에 뜨는게 더이상.. return mv; } session.setAttribute("no", NoAndName[0]); // 회원 번호 저장. session.setAttribute("name", NoAndName[1]); // 회원 번호 저장. session.setMaxInactiveInterval(60*30); // s(초) 단위. session이 존재하는 시간. 즉, 로그인 유지시간이다. web.xml 에서도 지정가능하며 거기는 분단위다. mv.setViewName("redirect:/contents");//forward 도 가능하지만 forward대상 컨트롤을 숨기기 때문에 요청이 남아있어 새로고침시 다시 자신 컨트롤이 작동한다. // redirect 해도 session저장에 문제 없으니 이렇게 하자. return mv; } @RequestMapping("logout") public ModelAndView process(HttpSession session) { if(session != null) { session.invalidate(); } ModelAndView mv = new ModelAndView(); mv.setViewName("redirect:/contents"); return mv; } }
3. member_ui
>> Profile
@Controller public class Profile{ IMemberDAO memberDAO = new MemberDAO(); @RequestMapping("profile/{no}")//profile?no=1 -> profile/1 로 바뀜 public ModelAndView process(@PathVariable int no) { Member member = memberDAO.findByNo(no); ModelAndView mv = new ModelAndView(); mv.setViewName("profile"); mv.addObject("profile", member.getProfile()); return mv; } }
@RequestMapping : {요청명에 들어가는 변하는 값}
@PathVariable : 요청명의(경로) 변수를 매핑 , 변수명과 설정한 요청명값(현재 no) 가 같지안다면 @PathVariable("no")까지 써야함>> 아이디중복검사컨트롤
@Controller public class 아이디중복검사컨트롤{ @Autowired MemberDAO dao; @GetMapping("id") public String process() { return "아이디중복검사창"; } @PostMapping("id") public ModelAndView process(String id) { boolean 사용가능여부 = false; String 메세지 = ""; if(id == "") { 메세지 = "아이디를 입력하세요."; }else if(id != null){ 사용가능여부 = dao.isin(id); 메세지 = (사용가능여부)?"사용가능한 ID입니다.":"이미 사용 중인 ID입니다."; } ModelAndView mv = new ModelAndView(); mv.addObject("msg", 메세지); mv.addObject("id", id); mv.addObject("result", 사용가능여부); mv.setViewName("아이디중복검사창"); return mv; } }
>> 회원등록컨트롤
@Controller public class 회원등록컨트롤{ @Autowired MemberDAO memberDAO; @GetMapping("member") public String process() { return "회원등록창"; } @PostMapping("member") public ModelAndView process(@ModelAttribute Member member,HttpSession session) {//객체 안의 멤버변수를 살펴서 변수명에 맞춰 매칭시켜줌 //@ModelAttribute 는 MultipartFile 형의 멤버변수를 가지고 있다면 선언해줘야 한다. ModelAndView mv = null; try { memberDAO.save(member); mv = new ModelAndView(); // 정보는 여기서 저장하고 나가기! Object[] NoAndName = memberDAO.isin(member.getId(), member.getPassword()); session.setAttribute("no", NoAndName[0]); session.setAttribute("name", NoAndName[1]); // 이 컨트롤에서 벗어나기! mv.setViewName("redirect:/contents"); } catch (Exception e) { e.printStackTrace(); } return mv; } @RequestMapping("result_member") public String process2() { return "회원등록결과통보"; } @RequestMapping("member/{no}") public ModelAndView process(@PathVariable("no")int 회원번호) {//요청이름과 변수명이 다르니 ("no")까지 써주기 ModelAndView mv = new ModelAndView(); Member findedMember = memberDAO.findByNo(회원번호); mv.addObject("findedMember", findedMember); mv.setViewName("회원상세창"); return mv; } @RequestMapping("member2/{no}") public ModelAndView process2(@PathVariable("no")int 회원번호) { ModelAndView mv = new ModelAndView(); Member findedMember = memberDAO.findByNo(회원번호); mv.addObject("findedMember", findedMember); //그림을 문자열로 바꿔 간다. String profile = Base64.encodeBytes(findedMember.getProfile()); mv.addObject("profile", profile); mv.setViewName("회원상세창2"); return mv; } }
if문 해석 1. 첨부리스트.childNodes.length : 첨부리스트 내의 li 들 몇갠지 2. 첨부리스트.childNodes.length - 1 : 마지막 li의 인덱스 3. 첨부리스트.childNodes[첨부리스트.childNodes.length - 1].childNodes[0].value : 마지막 li 의 input.file 의 값
4. VIEW
>> 게시물등록창 ( 주석설명 참고 )
<body> 제목 <input type="text" name="title" /> <br> 내용 <textarea name="title"></textarea> <br> 첨부 <br> <ul id="attachlist"></ul> <button onclick="첨부요소추가하다()">추가</button> </body> </html> <script type="text/javascript"> function 첨부요소추가하다(e) { // ul 을 가져온다. var 첨부리스트 = document.querySelector("#attachlist"); // ul 아래 자식요소(li)가 1개 이상은 있는데, // 마지막 li의 첫번째 자식인 input.file 에 값이 없을 때 if (첨부리스트.childNodes.length > 0 && 첨부리스트.childNodes[첨부리스트.childNodes.length - 1].childNodes[0].value == "") { return 0; } // ul 아래 들어갈 li, input.file, input.button 을 만든다. var li = document.createElement("li"); var fileInput = document.createElement("input"); fileInput.type = "file"; var btnDelete = document.createElement("input"); btnDelete.type = "button"; btnDelete.value = "삭제"; // containerLi 라는 속성을 직접 만들어 li 객체를 기억한다. // 과거의 li를 지목해서 가져올려면 어딘가에 저장해야 하기 때문이다. btnDelete.containerLi = li; btnDelete.addEventListener("click", function(event) { // 현재 클릭 이벤트가 발생한 btnDelete 의 containerLi속성에 저장한 li를 가져온다. var li=event.target.containerLi; // 저장한 li 정보의 부모인 ul 을 가져온다. var ul=li.parentNode; // ul 의 자식요소인 li 를 지운다. ul.removeChild(li); }) // li 아래에 input.file, input.button 을 넣는다. li.appendChild(fileInput); li.appendChild(btnDelete); // ul 아래에 위의 li 를 넣는다. 첨부리스트.appendChild(li); } </script>
>> 로그인상단
<body> <% if(session!=null){ %> <%=(String)session.getAttribute("name") %><i class="fas fa-user"></i><button onclick="location.href='logout'">로그아웃</button> <button onclick="location.href='member/<%=(int)session.getAttribute("no")%>'">개인정보</button> <button onclick="location.href='member2/<%=(int)session.getAttribute("no")%>'">개인정보/이미지글자형식</button> <%}else{ %> <button onclick="location.href='login'">로그인</button><!-- get방식. post라고 안써있으면 다 get방식이다. --> <button onclick="location.href='member'">회원등록</button> <%} %> </body>
>> 로그인창
<body> <h1>로그인</h1> <p>${message}</p> <form action="login" method="post" onsubmit="return 필수입력()"> id <input type="text" id="id" name="id"/> password <input type="password" id="password" name="password"/> <input type="submit" value="로그인"/><input type="button" onclick="location.href='member'" value="회원등록"> </form> </body>
>> 아이디중복검사창
<body> <form action="id" method="post"> 아이디 <input type="text" name="id" id="id" value="${id}" /> <input type="submit" value="검사"> </form> <%-- 결과 출력 --%> ${msg} <%if(result){ %> <br> <button onclick="id를보내다()">사용</button> <%} %> <button onclick="self.close()">취소</button> </body>
>> 회원등록창
<form action="member" onsubmit="return 회원등록하다()" method="post" enctype="multipart/form-data"> // 중략.. <script> /* 우편번호,주소 */ function 조회창을띄우다(){ window.open("prepare_address","","width=450,height=300"); } // 중략.. /* 아이디 */ function 아이디중복검사창을띄우다(){ window.open("id","","width=450,height=300"); }
>> 회원상세창
<body> <h1>회원상세</h1> 성명 <input type = "text" id="name" readonly="readonly" value="${findedMember.name}"><br> 프로필<img src="/profile/${findedMember.no}"/><br> 우편번호 <input type = "text" id="post" readonly="readonly" value="${findedMember.post}"><br> 주소 <input type = "text" readonly="readonly" value="${findedMember.address}"> <br> 상세주소 <input type = "text" readonly="readonly" value="${findedMember.detailaddress}"> <br> 전화번호 <input type = "text" readonly="readonly" value="${findedMember.tel}"/><br> 이메일 <input type = "text" readonly="readonly" value="${findedMember.email}"/><br> 아이디 <input type = "text" readonly="readonly" value="${findedMember.id}"><br> <button onclick="location.href='/contents'">내용창으로</button> </body>
5. 상세보기에서 이미지 경로와 다시 내용보기 경로가 오류가 나는 이유
상세보기 uri = mvc/member/n
이곳에서 src 또는 href 경로요청을 zz 라고 해놓으면
src 또는 href 경로요청의 uri = mvc/member/zz 식으로 가기 때문에 요청명이 'zz' 인 곳으로 가려했는데 아니여서 오류가 난다.
같은 위치에 요청명이 바뀐다. ( mvc/member/n 에서 n 부분)
요청명이 'member/zz' 인 걸 찾는다.
해결방법 1. 절대루트로 작성한다.(http://~)
해결방법 2. ' /' 를 맨앞에 붙이면 ip:port/ 부터 시작한다
(1) '/'(ip:port/ 다음 경로) (2) '../' ( 윗 경로 ), (3)'' = '../' ( 현재경로 )
1. tomcat 의 server.xml 에 현재 앱의 path 를 / 로 바꾸기.
<Context docBase="spring-mvc" path="/" reloadable="true" source="org.eclipse.jst.jee.server:spring-mvc"/></Host>
앞으로 주소창 요청이 http://localhost:8090/요청명 ( 프로젝트명 이제 안써도된다. )
2. 이미지를 불러올 src 요청명 맨앞에 '/' 를 붙이면 요청명을 ip:port/member/ 에서 경로를 하나 올라가서 ip:port/ 부터 시작한다.
ip:port/요청명 로 잘 매핑된다.
프로필<img src="/profile/${findedMember.no}"/><br>
'JAVA' 카테고리의 다른 글
Spring:댓글 (0) 2021.07.26 Spring:resolver (0) 2021.07.26 Spring:Mapping,Repository,Autowired(DI),multipartFile (0) 2021.07.26 Spring, Tomcat Servers의xml파일정리 (0) 2021.07.26 UML: ClassDiagram,SequenceDiagram, Servlet:Filter (0) 2021.07.26