board,member(spring.ver1)
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>