본문 바로가기
Kosta DevOps 과정 280기/Java

게시판 업그레이드

by 롯슈83 2024. 8. 12.

답변형(계층형) 게시판

  • 먼저 기존 데이터 삭제 후 칼럼 추가
  • b_ref :  서로 관련있는 글임을 나타내기 위한 글임을 설정
    • 만약 답글이 아닌 새 글이면 글 번호와 동일하게 설정한다.
    • 답글이면 부모를 글 번호가 값이 된다.
  • b_level : 답글의 계층(깊이)를 나타내기 위한 칼럼
    • 부모가 0이면1, 부모가 1이면 2가 된다.
    • 새 글이면 0이다.
  • b_step : 서로 관련있는 글일 때 글을 출력하기 위한 순서를 위한 칼럼
    • 부모가 같을 때 , 이미 달려있는 댓글을 1씩 증가시키고(새 답글이 맨 위로 가야하므로) 자신이 위에 달린다.
    • 새 글이면 자신이 최상위 부모가 되므로 0이다.
    • 답글은 물론 1부터 항상 시작된다.
  • vo 에 설정된 컬럼 3개 추가
package com.example.demo.vo;

import java.util.Date;

import org.springframework.web.multipart.MultipartFile;

import lombok.Data;

@Data
public class BoardVO {
	private int no;
	private String title;
	private String writer;
	private String pwd;
	private String content;
	private Date regdate;
	private int hit;
	private String fname;
	private int fsize;
	private String ip;
	private MultipartFile uploadFile;
	private int b_ref;
	private int b_level;
	private int b_step;
}
  • BoardMapper.xml 과 DB Manager dao 등에 게시물 번호를 변경하는 코드를 추가한다.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="board">
	<select id="nextNo" resultType="java.lang.Integer">
		select nvl(max(no), 0)+1 from borad
	</select>
  <select id="findAll" resultType="boardVO">
    select * from board
  </select>
  
  <insert id="insert" parameterType="boardVO">
  	insert into board values(#{no},#{title},#{writer},#{pwd},#{content},sysdate,0,#{fname},#{fsize},#{ip}, #{b_ref}, #{b_level}, #{b_step})
  </insert>
  
  <select id="findByNo" resultType="boardVO">
  	select * from board where no=#{no}
  </select>
 
 <update id="update" parameterType="boardVO">
 	update board set title=#{title},content=#{content},fname=#{fname},fsize=#{fsize} where no=#{no} and pwd=#{pwd}
 </update>
 
 <delete id="delete">
 	delete board where no=#{no} and pwd=#{pwd}
 </delete>
</mapper>
package com.example.demo.db;

import java.awt.print.Book;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.example.demo.vo.BoardVO;

//DBManager 클래스를 이용하여 기존 DAO 가 하던 작업을 대부분 여기서 진행한다.
public class DBManager {
	//SqlSessionFactory 객체는 mybatis 프레임워크에서 사용한다.
	//SqlSession 을 생성하는데 사용된다.
	public static SqlSessionFactory factory = null;
	//static 블록 안에서 아래와 같은 작업을 하면서, 애플리케이션 시작 시 한번만 초기화된다.
	static {
		//리소스 파일을 읽는 과정에서 예외가 발생할 수 있으므로 예외처리한다.
		try {
			//dbConfig 파일의 위치를 문자열로 저장
			String resource = "com/example/demo/db/dbConfig.xml";
			//Resources.getResourceAsStream(resource)는 Mybatis에서 리소스 파일을 스트림으로 읽어오는 유틸리티 메서드인데,
			//설정파일 또는 매퍼  xml 파일을 읽을 떄 사용된다.
			InputStream inputStream = Resources.getResourceAsStream(resource);

			//SqlSessionFactoryBuilder 클래스는 SqlSessionFactory 객체를 생성하는 빌더 클래스이다. 
			//설정 파일 즉, dbConfig 파일을 읽고 SqlSessionFactory 객체를 구성한다.
			factory =
					
			  new SqlSessionFactoryBuilder().build(inputStream);
			//inputStream 을 인수로 받아 설정파일을 읽고, 이 설정파일을 기반으로 SqlSessionFactory 객체를 생성한다. 
		}catch (Exception e) {
			System.out.println("예외발생:"+e.getMessage());
		}
	}
	
	public static List<BoardVO> findAll(){
		List<BoardVO>  list = null;
		//Sqlsession 을 생성한다.
		//SQql 세션은 트랜잭션을 관리하며, DB 상호 작용을 캡슐화하며, SQL 쿼리를 실행하고 매핑할 수 있다.
		SqlSession session = factory.openSession();
		//Mybatis Mapper 파일에서 board.finedAll 을 실행시킨다.
		list = session.selectList("board.findAll");
		//SqlSession 자원을 닫는다.
		//SqlSession 은 DB연결, 캐시, 커서 등 다양한 리소스를 사용하는데, 이 리소스는 제한적이라서 반드시 해제되어야 한다.
		session.close();
		//BoardVO 리스트들을 리턴한다.
		return list;
	}	
	
	public static int insert(BoardVO b) {
		//re 를 -1 초깃값으로 셋팅한다.
		int re = -1;
		//openSession 메소드에 true를 매개변수로 줄 경우 AutoCommit 이 true가 된다. 
		SqlSession session = factory.openSession(true);
		re = session.insert("board.insert", b);
//		session.commit();
		session.close();
		return re;
	}
	
	public static BoardVO findByNo(int no) {
		BoardVO b = null;
		SqlSession session = factory.openSession();
		b = session.selectOne("board.findByNo", no);
		session.close();
		return b;
	}
	
	public static int update(BoardVO b) {
		int re = -1;
		//AutoCommit 을 true 하겠다는 의미로 true 를 매개변수로 준다.
		SqlSession session = factory.openSession(true);
		re = session.update("board.update", b);
		session.close();
		return re;
	}
	
	public static int delete(int no, String pwd) {
		int re = -1;
		HashMap<String , Object> map = new HashMap<>();
		map.put("no", no);
		map.put("pwd", pwd);
		SqlSession session = factory.openSession(true);
		re = session.delete("board.delete", map);
		session.close();
		
		return re;
	}
	
	public static int getNextNo() {
		int no = 0;
		SqlSession session = factory.openSession(true);
		no = session.selectOne("board.nextNo");
		session.close();
		return no;
	}
}
package com.example.demo.dao;

import java.util.List;

import org.springframework.stereotype.Repository;

import com.example.demo.db.DBManager;
import com.example.demo.vo.BoardVO;

@Repository
public class BoardDAO {
	public List<BoardVO> findAll(){
		return DBManager.findAll();
	}
	
	public int insert(BoardVO b) {
		return DBManager.insert(b);
	}
	
	public BoardVO findByNo(int no) {
		return DBManager.findByNo(no);
	}
	
	public int update(BoardVO b) {
		return DBManager.update(b);
	}
	
	public int delete(int no, String pwd) {
		return DBManager.delete(no, pwd);
	} 
	public int getNextNo() {
		return DBManager.getNextNo();
	}
}

 

  • 새 글의 처리 - controller 에 insert 전에 다음 코드 추가
    • b_ref : 글 번호 no 와 동일하게 처리
    • b_level : 0 
    • b_step : 0
//새 글에 대한 처리
int no = dao.getNextNo();
int b_ref = no;
int b_level = 0;
int b_step = 0;

 

  • 답글의 처리 
  • b_ref : 부모글의 b_ref 와 동일하게 처리
  • b_level : 부모 글의 b_level +1
  • b_step : 이미 달려있는 답글들의 b_step 을 1씩 증가시킨 후에 b_step+1 로 처리 
    • detail.jsp 에 답글 작성 버튼 추가
<a href="insertBoard?no=${g.no}" class="btn btn-light">답글 작성</a>

 

  • 만약 2번글에 대하여 답글을 작성한다면 no/b_ref/b_level/b_step 이면, 4/2/1/1이 된다.
  • 만약 1번글에 대하여 답글을 작성한다면 no/b_ref/b_level/b_step 이면, 5/1/1/1이 된다.
  • 2번 글에 답글을 달면 변동이 있는 레코드는 다음과 같다.
    • 추가되는 레코드 no/b_ref/b_level/b_step 이면, 6/2/1/1이 된다.
    • 부모 레코드는 no/b_ref/b_level/b_step 이면, 2/2/1/0이 된다.
    • 2 2 0 0
      • 6 2 1 1
        • 7 6 2 2
      • 4 2 1 2
    • 3 3 0 0
    • 1 1 0 0
    • 5 1 1 1
<!-- Mapper 에 추가-->
<update id="updateStep">
    update board set b_step = b_step+1 where b_ref = #{b_ref} and b_step > #{b_step}
</update>
public void updateStep(HashMap map) {
    DBManager.updateStep(map);
    return;
}

 

 

메일 발송

7 스프링 메일발송.ppt
0.69MB

 

앱 비밀번호로 로그인 - Gmail 고객센터

중요: 앱 비밀번호 사용은 권장되지 않으며 대부분의 경우 필요하지 않습니다. 계정을 안전하게 보호하려면 'Google 계정으로 로그인'을 사용하여 앱을 Google 계정에 연결하세요. 앱 비밀번호란 보

support.google.com

 

인증 메일 입력 및 중복 체크까지 오늘의 최종 코드

디렉터리 구조

 

 

베이스 패키지(com.example.demo)

  • SpringConfig.java
package com.example.demo;

import java.util.Properties;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSenderImpl;

//환경설정 파일임을 어노테이션으로 명시
//이 클래스 안에 정의된 메서드 들이 Spring Bean으로 등록된다.
@Configuration
public class SpringConfig {
	
	//@Bean 어노테이션은 해당 메서드가 반환하는 객체가 Spring 컨테이너에 의해 관리되는 Bean이 됨을 의미
	@Bean
	public JavaMailSenderImpl javaMailSender() {
		//JavaMailSenderImpl 객체 생성한다. 이 객체는 이메일을 보낼 수 있는 기능을 제공한다. 
		JavaMailSenderImpl jms = new JavaMailSenderImpl();
		//메일 서버의 호스트 이름을 설정합니다. 여기서는 Gmail의 SMTP 서버(smtp.gmail.com)를 사용한다.
		jms.setHost("smtp.gmail.com");
		//Gmail 인 경우 Port번호는 587이다.
		//메일 서버의 포트 번호를 설정합니다
		jms.setPort(587);
		//이메일을 발송할 때 사용할 Gmail 계정의 사용자명을 설정한다.
		jms.setUsername("greylife5451");
		//이메일 발송 시 사용할 Gmail 계정의 비밀번호를 설정한다. 비밀번호는 안전하게 관리되어야 하며, 코드에 직접 노출하지 않는 것이 좋다.
		//Google의 강화된 보안 정책으로 2단계 인증을 먼저 하고, 앱 비밀번호 사용 관리에서 비밀번호를 발급받아 사용한다.
		jms.setPassword("hvqg vgty pbcs tljl");
		//이메일의 인코딩을 지정한다.
		jms.setDefaultEncoding("UTF-8");
		
		//키와 값으로 이루어진 프로퍼티 객체를 생성한다.
		Properties prop = new Properties();

		//TLS(Transport Layer Security) 활성화
		//TLS를 활성화하여 보안 연결을 사용하도록 설정합니다. TLS는 이메일 전송 시 보안을 강화합니다.
		prop.put("mail.smtp.starttls.enable", true);
		//SMTP 인증 사용 설정
		//SMTP 서버 인증을 사용하도록 설정합니다. 이메일을 보내기 전에 인증이 필요합니다.
		prop.put("mail.smtp.auth", true);
		//서버 인증 확인 설정
		//서버 인증을 확인하도록 설정합니다. 이는 서버의 SSL 인증서를 확인하는데 사용됩니다.
		prop.put("mail.smtp.ssl.checkserveridentity", true);
		//SSL 신뢰 설정
		//모든 서버의 SSL 인증서를 신뢰하도록 설정합니다. 보안상 위험할 수 있으므로 필요에 따라 수정이 필요합니다.
		prop.put("mail.smtp.ssl.trust", "*");
		//SSL 프로토콜 설정
		//SSL 프로토콜을 TLSv1.2로 설정합니다. TLSv1.2는 보안이 강화된 버전입니다.
		prop.put("mail.smtp.ssl.protocols", "TLSv1.2");
		
		//Properties 객체 설정
		//위에서 설정한 Properties 객체를 JavaMailSenderImpl 객체에 적용합니다.
		jms.setJavaMailProperties(prop);
		
		//JavaMailSenderImpl 객체 반환
		//설정된 JavaMailSenderImpl 객체를 반환하여 Spring 컨테이너에 Bean으로 등록합니다.
		//이 클래스는 주로 Spring 애플리케이션에서 이메일을 발송할 수 있도록 설정하는 데 사용됩니다.
		return jms;
	}
}

 

Controller

  • BoardController.java
package com.example.demo.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.dao.BoardDAO;
import com.example.demo.vo.BoardVO;

import jakarta.servlet.http.HttpServletRequest;

//info : 어노테이션 Controller를 설정한다.(URL을 매핑하여 뷰페이지를 반환한다)
//info : Controller역할을 부여하는데, Controller 는 요청을 받아 업무 로직을 호출하고, 적절한 View 를 호출하기 전까지 전체 처리 흐름을 제어한다.
@Controller
public class BoardController {
	
	//info : 어노테이션 Autowired 를 붙여서 인젝션을 지시한다.
	//info : 인젝션 지시 (주입 지시) 
	@Autowired
	private BoardDAO dao;
	
	//info : Get 방식으로 RequestMapping 한다. 이것은 아래와 같다.
	//info : 기본 페이지 index 페이지를 Mapping 한다. 이것은 아래와 같다.
	@GetMapping("/")
	public String index() {
		return "index";
	}
	
	//info : Get 방식으로 Request Mapping 한다. 이것은 아래와 같다.
	@GetMapping("/listBoard")
	public void list(Model model) {
		//listBoard.jsp 화면에 list 를 상태 유지한다.
		model.addAttribute("list", dao.findAll());
	}
	
//	@RequestMapping(value = "/insertBoard", method = RequestMethod.GET)
//	public void insertForm() {
//		
//	}
//	
//	@RequestMapping(value = "/insertBoard", method = RequestMethod.POST)
//	public ModelAndView insertSubmit() {
//		
//	}
	
	
	//info : 이렇게 되면 Get 방식을 Mapping 한다.
	//info : 게시물 작성 폼을 사용자에게 보여주기 위한 용도로 사용된다.
	//info : 뷰를 만환하지 않지만 Spring 에서는 맵핑된 jsp 뷰 페이지가 있으면 자동으로 뷰를 찾을 수 있다.
	//info : @RequestParam은 no의 값이 지정되지 않았을 때, 즉 null 일 때 defaulteValue의 기본값은 0으로 지정하는 것이다.
	//info : detailBoard 페이지에서 답글 수정을 누르면 게시물의 no 값이 전달이 되므로 0이 아닌 부모의 no 값이 들어오게 된다.
	//info : 반면, 새 글 입력은 listBoard.jsp 화면에서 게시물 등록 버튼을 눌렀을 때로, 전달하는 값이 없으므로 null이 된다.
	//info : 따라서 답글 등록이 아닌 새 글 등록은 아래의 @RequestParam 에 의하여 0으로 셋팅되는 것이다.
	@GetMapping("/insertBoard")
	public void insertForm(@RequestParam(value="no", defaultValue="0") int no, Model model) {
		
		//no 값을 상태 유지한다.
		model.addAttribute("no", no);
		
		//게시물 등록으로 미리 title 지정
		String title = "게시물 등록";
		
		// 위의 규칙으로 no 가 0이 아니면 답글 작성인 것을 알 수 있다. 따라서 아래로 title 을 설정해준다.
		if(no != 0) {
			title = "답글 작성";
		}
		
		//title 을 상태 유지한다.
		model.addAttribute("title", title);
	}

	//info : 이렇게 되면 Post 방식을 Mapping 한다.
	//info : 사용자가 작성한 게시물 데이터를 서버로 전송했을 때 그 데이터를 처리하는 역할을 맡는다.
	@PostMapping("/insertBoard")
	public ModelAndView insertSubmit(BoardVO b, HttpServletRequest request) {
		//info_top : ModelAndView 를 반환한다. 이 때, VO를 매개변수로 넣고, HttpServletRequest를 매개변수로 만든다. 이처럼 필요에 따라 request, response 등을 매개변수로 가져올 수 있다.
		
		//info : 요청 정보를 담을 request 객체에서 ServletContext를 얻는다.
		//info : ServletContext는 웹 애플리케이션 전역정보를 담고있다.
		//info : 가상경로를 실제 파일 시스템 경로로 반환한다.(upload 폴더 실제 경로 반환)
		String path = request.getServletContext().getRealPath("upload");
		
		//info : path 를 콘솔에 찍어본다.
		System.out.println("path:"+path);
		
		//info : fname 및 fsize 등등을 초깃값으로 셋팅한다.
		String fname = null;
		int fsize = 0;
		
		//info : BoardVO에서 UploadFile 속성값을 얻어온다.
		//info : MultipartFile는 Spring에서 파일 업로드를 처리할 때 사용하는 인터페이스이다.
		//info : 뷰페이지 폼에서 전송된 파일 데이터를 다룰 때 사용된다.
		MultipartFile uploadFile = b.getUploadFile();
		
		//info : getOriginalFilename() 메서드는 클라이언트가 업로드한 파일의 원래 이름을 반환한다.
		fname = uploadFile.getOriginalFilename();
		byte[]data = null;
		
		//getBytes() 또한 IO Exception 을 품고 있으므로 예외처리를 해준다. 
		try {
			//업로드한 파일의 내용을 Byte 배열로 얻어온다.
			data = uploadFile.getBytes();
			
			//Byte 배열의 크기를 재서 fsize 를 얻는다.
			fsize = data.length;
		}catch (IOException e) {
			System.out.println("예외발생:"+e.getMessage());
		}
		
		//BoradVO 에 fname 과 fsize의 속성 값을 넣어준다.
		b.setFname(fname);
		b.setFsize(fsize);		
		
		//요청 정보에서 클라이언트(사용자)의 IP 주소를 얻는다. 
		String ip = request.getRemoteAddr();
		//VO 에 ip 속성 값을 넣어준다.
		b.setIp(ip);
		
		//no 값을 가져온다.
		//detail.jsp 에서 타고 들어온 경우에는 선택한 게시글의 no 값을 가져오게 된다.
		//반면, 완전히 새 글을 작성하는 경우에는 초깃값 0이 셋팅된다.
		//즉 답글일 경우에는 부모의 no 값을 가져오고, 새글 작성의 경우 0이 셋팅되는 것이다.
		int pno = b.getNo();
		
		//새 글에 대한 처리
		//no는 현재 글의 글 번호가 된다.
		int no = dao.getNextNo();
		//b_ref는 no의 값으로 셋팅한다.
		int b_ref = no;
		//새 글이므로 level 은 0으로 셋팅한다.
		int b_level = 0;
		//새 글이므로 step 도 0으로 셋팅한다.
		int b_step = 0;
		
		//답글에 대한 처리 / 답글이 아니면 pno 0 답글이면 부모의 no 를 가지게 됨
		if(pno != 0) {
			//부모의 no값에 따른 p즉, 부모의 게시글 정보를 찾음
			BoardVO p = dao.findByNo(pno);
			//부모의 b_ref 값으로 셋팅
			b_ref = p.getB_ref();
			//부모의 레벨값으로 먼저 셋팅
			b_level = p.getB_level();
			//부모의 step값으로 먼저 셋팅
			b_step = p.getB_step();

			//여기서 step은 자기를 제외한 다른 부모의 답글들의 step(순서)를 뒤로 하나씩 밀고 자신이 그 자리에 들어간다.
			//따라서 먼저 Map 을 만들어 sql 에서 작업을 해준다.
			//b_ref 는 부모가 한데로 엮여있는 다른 답글들을 찾기 위해서 
			//b_step은 하나씩 차례로 전부 밀기 위해서 값을 넘겨주는 것이다.
			HashMap map = new HashMap();
			map.put("b_ref", b_ref);
			map.put("b_step", b_step);
			//dao 에서 updateStep 메소드에 map 을 넘겨준다.
			dao.updateStep(map);
			
			//또한, 우선 자기 자신의 b_level(dept)과 b_step(순서)를 부모보다 1  순위로 밀어 넣는다.
			//위의 Map 파일로 하나씩 미는 것은 자기 자신을 제외한 다른 답글들의 조정이므로 아래와 같이 1씩 더하는 연산을 하는 것이다.
			b_level++;
			b_step++;
		}
		
		b.setNo(no);
		b.setB_ref(b_ref);
		b.setB_level(b_level);
		b.setB_step(b_step);
		
		//데이터베이스에 값을 삽입한다.
		int re = dao.insert(b);
		
		//re는 삽입된 행의 갯수이므로 1일경우 insert를 성공했다는 뜻이다. 즉, 성공하면 and
		//기본 값은 null 값 허용이므로 업로드 파일이 없을 수도 있다. 즉, 삽입도 성공하고 업로드 파일이 있다면
		//업로드 된 파일의 이름이 비어있다면 업로드 된 파일이 이상한 것이므로 제대로 파일이 올라갔는지 여부를 판단한다.
		if(re == 1 && fname != null && !fname.equals("")) {
			//FileOutputStream은 FileNotFoundException과 IO Exception 등의 예외를 품고 있으므로 예외 처리를 해준다.
			try {
				//File 출력 스트림 객체를 생성하고 매개변수로 파일의 경로와 이름을 넣어준다.
				//FileNotFoundException 이 발생할 수 있다.
				FileOutputStream fos = new FileOutputStream(path + "/"+ fname);
				//IO Exception 이 발생할 수 있다.
				fos.write(data);
				//자원을 닫아준다.
				fos.close();
			}catch (Exception e) {
				System.out.println("예외발생:"+e.getMessage());
			}
		}
		
		//ModelAndView 를 생성한다. 이 때 생성시에 매개변수로 바로 setName해줄 수 있다.
		ModelAndView mav = new ModelAndView("redirect:/listBoard");
		
		//만약 등록에 실패했을 경우
		if(re <= 0) {
			//오류 메세지를 상태유지한다.
			mav.addObject("msg", "게시물 등록에 실패");
			//에러페이지로 이동
			mav.setViewName("error");
		}
		//뷰 페이지를 리턴한다.
		return mav;
	}
	
	//해당 페이지로 Get방식일 때 동작할 메소드 
	@GetMapping("/detailBoard")
	public void detail(Model model, int no) {
		//detail 페이지로 VO 를 상태 유지 시킨다.
		model.addAttribute("b", dao.findByNo(no));
	}
	
	//삭제 페이지 Get방식으로 요청했을 때로, Form 을 보여준다.
	@GetMapping("/deleteBoard")
	public void deleteForm(Model model, int no) {
		//이 때, 게시판 번호를 상태 유지한다.
		model.addAttribute("no", no);
	} 
	
	//jsp에서 작성하여 Post 방식으로 보냈던 no 값과 pwd 값을 가져온다.
	@PostMapping("/deleteBoard")
	public ModelAndView deleteSubmit(int no, String pwd, HttpServletRequest request) {
		//게시물 삭제시키기 전에 파일 이름을 먼저 알아놓는다.
		String oldFname = dao.findByNo(no).getFname();
		//ServletContext 의 경로를 가져와서 upload폴더의 경로를 path 변수에 담는다.
		String path = request.getServletContext().getRealPath("upload");
		
		//성공하면 list 화면으로 이동하도록 기본 설정
		ModelAndView mav = new ModelAndView("redirect:/listBoard");
		
		//dao 의 delete 메소드 설정
		int re = dao.delete(no, pwd);
		
		if(re <= 0) {
			//삭제에 실패하면 에러 메세지를 상태유지한다.
			mav.addObject("msg", "게시물 삭제에 실패하였습니다. 비번확인 요망");
			//삭제에 실패하면 error 화면으로 이동
			mav.setViewName("error");
		}else {
			//원래 파일이 있는지 확인
			if(oldFname != null && !oldFname.equals("")) {
				//파일 객체를 생성한다.
				File oldFile = new File(path+"/"+oldFname);
				//파일 객체의 제공되는 메소드를 이용하여 파일을 삭제한다.
				oldFile.delete();
			}
		}
		
		return mav;
	}
}
  • MemberController
package com.example.demo.controller;

import java.util.Random;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.example.demo.dao.MemberDAO;
import com.example.demo.vo.MemberVO;

//이 객체는 Controller 의 역할을 한다고 애너테이션으로 지정한다.
@Controller
public class MemberController {
	//IoC 로 자동으로 생성하여 DI 로 자동으로 매칭해준다. 
	@Autowired
	private MemberDAO dao;
	
	//MailSender Spring Framework 에서 이메일 전송을 위한 인터페이스로 이메일을 보내기 위한 기본적인 메서드를 정의하고 있다.
	//send() 메서드를 사용하여 이메일 메시지를 전송할 수 있다.
	@Autowired
	private MailSender mailSender;
	
	//Get방식으로 해당 url이 매핑될 떄 메소드를 정의한다.
	@GetMapping("/join")
	public void joinForm() {
		//단순히 join 페이지로 이동된다.
	}
	
	//Post 방식으로 해당 url 이 매핑될 떄 메소드를 정의한다.
	//ResponseBody 는 메서드의 반환값을 HTTP 응답 본문에 직접 작성하도록 지정한다.
	//즉, 반환된 데이터가 뷰 이름으로 해석되지 않고, 그대로 클라이언트에게 전송된다.
	//주로 RESful 웹서비스에서 JSON 또는 XML 형식의 데이터를 반환할 때 사용된다.
	//이 에너테이션을 사용하면 컨트롤러 메서드에서 반환하는 데이터가 HTTP 응답으로 직렬화되어 클라이언트에게 전달된다.
	@PostMapping("/join")
	@ResponseBody
	public String JoinSubmit(MemberVO m) {
		//dao에서 멤버의 값을 삽입한다.
		int re = dao.join(m);
		//삽입 실패시 에러 발생 출력문을 출력한다.
		if(re < 0) {
			System.out.println("멤버 추가 에러 발생:");
		}
		//"OK"를 응답하는데, 위의 ResponseBody 애너테이션으로 인하여 View 페이지 이동이 아니라 해당 문자열이 클라이언트에게 그대로 전달된다.
		return "OK";
	}
	
	//Get방식으로 /sendCode 가 매핑될 떄 해당 메소드가 실행된다.
	//이 떄, ResponseBody 로 data가 그대로 직렬화되어 클라이언트에게 전송된다.
	@GetMapping("/sendCode")
	@ResponseBody
	public String sendCode(String email) {
		String data="";
		
		//SimpleMailMessage 객체는 Spring Framework 에서 이메일을 간단하게 작성하고 전송할 수 있도록 도와주는 클래스
		//텍스트 기반 이메일 메시지를 구성하기 위해 사용되며, 첨부 파일이나 HTML 콘텐츠와 같은 복잡한 기능은 지원하지 않는다
		SimpleMailMessage mailMessage = new SimpleMailMessage();
		//랜덤 객체 생성
		Random r = new Random();
		
		//랜덤 객체를 이용하여 0~9까지의 4자리 수를 data 에 담는다.
		data = r.nextInt(10)+""+r.nextInt(10)+""+r.nextInt(10)+""+r.nextInt(10);
		
		//이메일 제목 설정
		mailMessage.setSubject("인증코드 전송");
		//발신자의 이메일 주소 설정
		mailMessage.setFrom("greylife5451@gmail.com");
		//수신자의 이메일 주소 설정
		//여기서는 jsp 에서 넘어온 email 이 매개변수로 자동으로 받아져서 셋팅된다.
		mailMessage.setTo(email);
		//data를 이메일 본문 내용으로 설정한다.
		mailMessage.setText(data);
		
		try {
			//위에서 생성했던 MailSender 객체를 통하여 위에서 셋팅했던 mailMessage를 전송한다.
			mailSender.send(mailMessage);
		} catch (Exception e) {
			//예외 출력
			System.out.println(e);
		}
		
		//추후에 data 와 사용자가 입력한 값과 비교해야 하므로 data 값을 리턴한다.
		return data;
	}
	
	//idChk를 Get 방식으로 Mapping 할 떄 다음의 메소드가 실행된다.
	@GetMapping("/idChk")
	@ResponseBody
	public boolean idChk(String userid) {
		//dao의 findById를 통하여 vo 를 찾는다.
		MemberVO vo = dao.findById(userid);
		
		//이때 vo 가 null 이면 해당 id를 가진 회원이 없다는 것을 뜻한다.
		if(vo == null) {
			//사용할 수 있다는 의미로 true를 반환한다.
			return true;
		}
		
		//null 값이 아니면 해당 회원이 있다는 뜻이므로 사용할 수 없다는 의미로 false 를 반환한다.
		return false;
	}
	
}
  • UpdateBoardController
package com.example.demo.controller;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import com.example.demo.dao.BoardDAO;
import com.example.demo.vo.BoardVO;

import jakarta.servlet.http.HttpServletRequest;

//컨트롤러로 지정하되 updateBoard 를 요청하면 다음 클래스로 이동
@Controller
@RequestMapping("/updateBoard")
public class UpdateBoardController {
	
	//dao를 주입한다.
	@Autowired
	private BoardDAO dao;
	
	//위에서 요청값을 적어줬으므로 여기서는 요청 방식만 적어준다.
	//즉, Get 방식일 때 다음의 메소드가 실행된다. 
	@RequestMapping(method = RequestMethod.GET)
	public void form(Model model, int no) {
		//VO의 내용을 상태 유지한다.
		model.addAttribute("b", dao.findByNo(no));
	}
	
	//Post 방식일 떄 다음의 메소드가 실행된다.
	@RequestMapping(method = RequestMethod.POST)
	public ModelAndView submit(BoardVO b, HttpServletRequest request) {
		//upload 디렉토리의 경로를 path 변수에 담는다.
		String path = request.getServletContext().getRealPath("upload");
		//기본 ModelAndView페이지를 listBoard 페이지로 지정한다.
		ModelAndView mav = new ModelAndView("redirect:/listBoard");
		
		//원래의 파일이름과 크기를 미리 담아둔다.
		//수정하고 나면 기존 파일을 삭제하기 위하여 저장해두는 것이다.
		String oldFname = b.getFname();
		int oldFsize = b.getFsize(); 
		
		//기본 파일의 이름, 사이즈, 내용에 초깃값을 준다.
		String fname= null;
		int fsize = 0;
		byte []data = null;
		
		//업로드한 파일의 정보를 가지고 온다.
		MultipartFile uploadFile = b.getUploadFile();
		
		//파일 업로드 하는 것이 없음에도 불구하고 null 이 아니라 어떤 객체를 갖고 있으므로 null 이다라고 판단할 수 없다.
		//그래서 업로드 한 파일의 이름을 가지고와서 판단하는 것이다.
		fname = uploadFile.getOriginalFilename();
		
		//업로드 한 파일 이름으로 제대로 올라갔는지 먼저 판단
		if(fname != null && !fname.equals("")) {
			try {
				//제대로 올라갔다면 내용을 읽는다.
				data = uploadFile.getBytes();
				//file 의 크기를 배열의 크기를 재서 바이트 크기를 잰다.
				fsize = data.length;
				//VO 객체에 파일 이름 속성값을 준다.
				b.setFname(fname);
				//VO 객체에 파일 사이즈 속성값을 준다.
				b.setFsize(fsize);
			} catch (IOException e) {
				System.out.println(e);
			}
		}
		//db에 수정을 요청한다.
		int re = dao.update(b);
		
		//수정에 성공했고, 파일도 수정했다면 파일을 복사해준다.
		if(re >= 1 && fname != null && !fname.equals("")) {
			try {
				//파일 출력하기 위한 스트림을 생성한다.
				FileOutputStream fos = new FileOutputStream(path + "/" + fname);
				
				//파일에 데이터를 기록한다.
				fos.write(data);
				
				//스트림을 닫아준다.
				fos.close();
				
				//원래 파일이 있는지 확인
				if(oldFname != null && !oldFname.equals("")) {
					//파일 객체를 생성한다.
					File oldFile = new File(path+"/"+oldFname);
					//파일 객체의 제공되는 메소드를 이용하여 파일을 삭제한다.
					oldFile.delete();
				}
			} catch (Exception e) {
				System.out.println(e);
			}
		}
		
		//수정에 실패했다면 
		if(re <= 0) {
			//수정 실패 메세지
			mav.addObject("msg", "게시물 수정에 실패");
			//에러 페이지로 이동
			mav.setViewName("error");
		}
		
		//수정작업이 완료되면 게시판 목록 화면으로 돌아간다.
		return mav;
	}
}

 

DAO

  • BoardDAO
package com.example.demo.dao;

import java.util.HashMap;
import java.util.List;

import org.springframework.stereotype.Repository;

import com.example.demo.db.DBManager;
import com.example.demo.vo.BoardVO;
import com.example.demo.vo.MemberVO;

@Repository
public class BoardDAO {
	public void updateStep(HashMap map) {
		DBManager.updateStep(map);
		return;
	}
	public List<BoardVO> findAll(){
		return DBManager.findAll();
	}
	
	public int insert(BoardVO b) {
		return DBManager.insert(b);
	}
	
	public BoardVO findByNo(int no) {
		return DBManager.findByNo(no);
	}
	
	public int update(BoardVO b) {
		return DBManager.update(b);
	}
	
	public int delete(int no, String pwd) {
		return DBManager.delete(no, pwd);
	} 
	public int getNextNo() {
		return DBManager.getNextNo();
	}
}
  • MemberDAO
package com.example.demo.dao;

import org.springframework.stereotype.Repository;

import com.example.demo.db.DBManager;
import com.example.demo.vo.MemberVO;

@Repository
public class MemberDAO {
	public int join(MemberVO m) {
		return DBManager.join(m);
	}
	
	public MemberVO findById(String id) {
		return DBManager.findById(id);
	}
}

 

DB

  • DBManager
package com.example.demo.db;

import java.awt.print.Book;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import com.example.demo.vo.BoardVO;
import com.example.demo.vo.MemberVO;

//DBManager 클래스를 이용하여 기존 DAO 가 하던 작업을 대부분 여기서 진행한다.
public class DBManager {
	//SqlSessionFactory 객체는 mybatis 프레임워크에서 사용한다.
	//SqlSession 을 생성하는데 사용된다.
	public static SqlSessionFactory factory = null;
	//static 블록 안에서 아래와 같은 작업을 하면서, 애플리케이션 시작 시 한번만 초기화된다.
	static {
		//리소스 파일을 읽는 과정에서 예외가 발생할 수 있으므로 예외처리한다.
		try {
			//dbConfig 파일의 위치를 문자열로 저장
			String resource = "com/example/demo/db/dbConfig.xml";
			//Resources.getResourceAsStream(resource)는 Mybatis에서 리소스 파일을 스트림으로 읽어오는 유틸리티 메서드인데,
			//설정파일 또는 매퍼  xml 파일을 읽을 떄 사용된다.
			InputStream inputStream = Resources.getResourceAsStream(resource);

			//SqlSessionFactoryBuilder 클래스는 SqlSessionFactory 객체를 생성하는 빌더 클래스이다. 
			//설정 파일 즉, dbConfig 파일을 읽고 SqlSessionFactory 객체를 구성한다.
			factory =
					
			  new SqlSessionFactoryBuilder().build(inputStream);
			//inputStream 을 인수로 받아 설정파일을 읽고, 이 설정파일을 기반으로 SqlSessionFactory 객체를 생성한다. 
		}catch (Exception e) {
			System.out.println("예외발생:"+e.getMessage());
		}
	}
	
	public static List<BoardVO> findAll(){
		List<BoardVO>  list = null;
		//Sqlsession 을 생성한다.
		//SQql 세션은 트랜잭션을 관리하며, DB 상호 작용을 캡슐화하며, SQL 쿼리를 실행하고 매핑할 수 있다.
		SqlSession session = factory.openSession();
		//Mybatis Mapper 파일에서 board.finedAll 을 실행시킨다.
		list = session.selectList("board.findAll");
		//SqlSession 자원을 닫는다.
		//SqlSession 은 DB연결, 캐시, 커서 등 다양한 리소스를 사용하는데, 이 리소스는 제한적이라서 반드시 해제되어야 한다.
		session.close();
		//BoardVO 리스트들을 리턴한다.
		return list;
	}	
	
	public static int insert(BoardVO b) {
		//re 를 -1 초깃값으로 셋팅한다.
		int re = -1;
		//openSession 메소드에 true를 매개변수로 줄 경우 AutoCommit 이 true가 된다. 
		SqlSession session = factory.openSession(true);
		re = session.insert("board.insert", b);
//		session.commit();
		session.close();
		return re;
	}
	
	public static BoardVO findByNo(int no) {
		BoardVO b = null;
		SqlSession session = factory.openSession();
		b = session.selectOne("board.findByNo", no);
		session.close();
		return b;
	}
	
	public static int update(BoardVO b) {
		int re = -1;
		//AutoCommit 을 true 하겠다는 의미로 true 를 매개변수로 준다.
		SqlSession session = factory.openSession(true);
		re = session.update("board.update", b);
		session.close();
		return re;
	}
	
	public static int delete(int no, String pwd) {
		int re = -1;
		HashMap<String , Object> map = new HashMap<>();
		map.put("no", no);
		map.put("pwd", pwd);
		SqlSession session = factory.openSession(true);
		re = session.delete("board.delete", map);
		session.close();
		
		return re;
	}
	
	public static int getNextNo() {
		int no = 0;
		SqlSession session = factory.openSession(true);
		no = session.selectOne("board.nextNo");
		session.close();
		return no;
	}
	
	public static void updateStep(HashMap map) {
		SqlSession session = factory.openSession();
		session.update("board.updateStep",map);
		session.commit();
		session.close();
	}
	
	public static int join(MemberVO m) {
		int re = -1;
		SqlSession session = factory.openSession(true);
		re = session.insert("member.insert", m);
		session.close();
		return re;
	}
	
	public static MemberVO findById(String id){
		MemberVO vo = null;
		SqlSession session = factory.openSession();
		vo = session.selectOne("member.findById",id);
		session.close();
		return vo;
	}
}
  • BoardMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="board">
	<!-- 
	update board에서 step 값을 하나씩 올리는데, 조건은 부모의 no가 기존 no인 것과 b_step 이 기존 step 보다 큰 것들을 1씩 올린다.
	물론 새 답글도 부모 렙에 맞춰 1씩 올라간다. 
	#{b_ref}에서 부모가 최상위이면 b_no 값과 같으므로 된다.
	#{b_ref}에서 하위 이면 뒤의 b_step 조건으로 판별하는 것이다.
	ref와 부모 자신의 dept 와 같은 것들은 b_step의 값이 올라가면 안되므로 b_step > #{b_step} 이 되는것이다.
	여기서 ref 란 무조건 최상위이고, step 은 ref 끼리 묶인다. 또한, level이 dept 를 담당하는 것이다.
	즉, step 과 dept 는 상관이 없다.
	 -->
	<update id="updateStep">
		update board set b_step = b_step+1 
		where b_ref = #{b_ref} and b_step > #{b_step}
	</update>
	<select id="nextNo" resultType="java.lang.Integer">
		select nvl(max(no), 0)+1 from board
	</select>
  <select id="findAll" resultType="boardVO">
    select * from board order by b_ref desc, b_step
  </select>
  
  <insert id="insert" parameterType="boardVO">
  	insert into board values(#{no},#{title},#{writer},#{pwd},#{content},sysdate,0,#{fname},#{fsize},#{ip}, #{b_ref}, #{b_level}, #{b_step})
  </insert>
  
  <select id="findByNo" resultType="boardVO">
  	select * from board where no=#{no}
  </select>
 
 <update id="update" parameterType="boardVO">
 	update board set title=#{title},content=#{content},fname=#{fname},fsize=#{fsize} where no=#{no} and pwd=#{pwd}
 </update>
 
 <delete id="delete">
 	delete board where no=#{no} and pwd=#{pwd}
 </delete>
</mapper>
  • MemberMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="member">
 <insert id="insert" parameterType="memberVO">
 	insert into member values(#{id}, #{pwd}, #{m_name}, #{email}, #{phone})
 </insert>
 <select id="findById" resultType="memberVO">
 	select * from member where id=#{id}
 </select>
</mapper>
  • db.Config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <properties resource="com/example/demo/db/db.properties"/>
  <typeAliases>
  	<typeAlias type="com.example.demo.vo.BoardVO" alias="boardVO"/>
  	<typeAlias type="com.example.demo.vo.MemberVO" alias="memberVO"/>
  </typeAliases>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="com/example/demo/db/BoardMapper.xml"/>
    <mapper resource="com/example/demo/db/MemberMapper.xml"/>
  </mappers>
</configuration>
  • db.properties
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:XE
username=c##madang
password=madang
  • application.properties
spring.application.name=boardtests
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

 

VO

  • BoardVO
package com.example.demo.vo;

import java.util.Date;

import org.springframework.web.multipart.MultipartFile;

import lombok.Data;

@Data
public class BoardVO {
	private int no;
	private String title;
	private String writer;
	private String pwd;
	private String content;
	private Date regdate;
	private int hit;
	private String fname;
	private int fsize;
	private String ip;
	private MultipartFile uploadFile;
	private int b_ref;
	private int b_level;
	private int b_step;
}
  • MemberVO
package com.example.demo.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MemberVO {
	private String id;
	private String pwd;
	private String m_name;
	private String email;
	private String phone;
}

 

WEB-INF/views

  • deleteBoard.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>
	<h2>게시물 삭제</h2>
	<hr>
	<!-- post 방식으로 deleteBoard 쪽으로 데이터를 보낸다. -->
	<form action="deleteBoard" method="post">
		<!-- pwd 값을 받는다 -->
		글비번 : <input type="password" name="pwd"><br>
		<!-- 상태 유지 했었던 게시판 번호를 전달하기 위해 hidden 타입을 사용한다. -->
		<input type="hidden" name="no" value="${no}">
		<input type="submit" value="삭제">
	</form>
</body>
</html>
  • 게시물 상세
<%@ 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>
	<h2>게시물 상세</h2>
	<hr>
	글번호 : ${b.no }<br>
	글제목 : ${b.title }<br>
	글내용 : <br>
	<textarea rows="10" cols="80">${b.content }</textarea><br>
	등록일 : ${b.regdate }<br>
	ip: ${b.ip }<br>
	<!-- b.no데이터를 넘겨준다. -->
	<a href="updateBoard?no=${b.no}">수정</a>&nbsp;
	<a href="deleteBoard?no=${b.no}">삭제</a>&nbsp;
	<!-- 답글 작성은 현재 detailBoard 에 해당하는 게시글 번호를 넘겨주게 되므로 부모 no 값을 넘겨주게 된다. -->
	<a href="insertBoard?no=${b.no}" class="btn btn-light">답글 작성</a>
</body>
</html>
  • emailChk.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>
	<h2>이메일 인증</h2>
	<p>이메일로 간 인증 번호를 입력해주세요</p>
	<hr>
	<form method="post" action="join">
		인증번호 : <input type="text" name="emailChkNums">
	</form>
</body>
</html>
  • error.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>
	<h4>죄송합니다. 서비스 중에 다음과 같은 문제가 발생하였습니다.</h4>
	<hr>
	${msg }
</body>
</html>
  • index.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>
	<a href="listBoard">게시물 목록</a><br>
	<a href="insertBoard">게시물 등록</a><br>
	<a href="join">회원 등록</a><br>
</body>
</html>
  • insertBoard.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>
	<h2>${title}</h2>
	<hr>
	<form action="insertBoard" method="post" enctype="multipart/form-data">
	<input type="hidden" name="no" value="${no}">
		글제목 : <input type="text" name="title"><br>
		작성자 : <input type="text" name="writer"><br>
		글암호 : <input type="password" name="pwd"><br>
		글내용 : <br>		
		첨부파일 : <input type="file" name="uploadFile"><br>
		<textarea rows="10" cols="80" name="content"></textarea><br>		
		<input type="submit" value="등록">
		<input type="reset" value="다시입력">
	</form>
</body>
</html>
  • join.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>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
  <script>
  	$(function(){
  		let emailChkNums;		//Controller 쪽에서 넘어온 랜덤 data 값
  		let idChkOk = false;	//id중복 체크했는지 여부
  		let email;  			//인증 완료한 이메일
  		
  		//이메일 인증 버튼을 눌렀을 때, 
  		$("#btnSendEmail").click(function(){
  			//이메일 값을 email 에 담고, data 객체를 생성한다.
  			email = $("#email").val();
  			let data = {email:email};
  			
  			//Ajax 통신으로 /sendCode 에 Get 방식으로 통신하여, data 즉, email 값을 보낸다.
  			$.ajax({
  				url:"/sendCode",
  				data:data,
  				success:function(data){
  					//성공적으로 통신이 되었다면 받아온 데이터(data) emailChkNum 에 넣는다.(랜덤값)
  					//또한 인증번호 적는 영역을 css 로 보여준다.
  					$("#box_check").css("display", "block");
  					emailChkNums = data;
  				}
  			});
  		})
  		
  		//인증 요청을 눌렀을 때
  		$("#btnCheckEmail").click(function(){
  			//user가 적은 인증번호 값을 가져온다.
  			let userInNums = $("#checkNum").val();
  			//그리고 Controller 에서 넘어온 data 값이랑 같은지 비교하여
  			if(userInNums == emailChkNums){
  				//같으면 나머지 회원가입 부분도 작성시킨다.
  				$("#loginForm").css("display", "block");
  			}else{
  				//다르면 알림창을 띄운다.
  				alert("인증번호가 다릅니다.");
  			}
  		});
  		
  		//중복체크 버튼을 누르면
  		$("#btnIsUnike").click(function(e){
  			//먼저 form안에 button 이 있으므로 이벤트를 중지 시킨다
  			e.preventDefault();
  			//id  의 값을 가져오고
  			let id = $("#id").val();
  			//ajax 통신으로 id 값을 보낸다.
  			$.ajax({
  				url:"/idChk",
  				data:{userid:id},
  				success:function(data){
  					//받아온 값이 true 냐 false 냐에 따라 다음과 같이 나눠서 작업한다.
  					if(data){
  						//알림창으로 사용 가능 여부를 보낸다.
  						alert("사용 가능 아이디");
  						//emailOK 필드에 기존의 인증 완료한 email 값을 넣어준다.
  						$("#emailOK").val(email);
  						//id 체크를 완료했다는 플래그를 true 로 변경한다.
  						idChkOk = true;
  					}else{
  						//알림창으로 중복임을 알린다.
  						alert("중복 아이디");
  						//emailOK 부분에 필드 값을 비운다. 
  						$("#emailOK").val("");
  						//id 체크를 완료했다는 플래그를 false 로 변경한다.
  						idChkOk = false;
  					}
  				}
  			});
  		});
  		
  		//멤버 등록 버튼을 누르면
  		$("#btnSubmit").click(function(e){
  			//id 중복체크 플래그가 false이면 
  			if(!idChkOk){
  				//submit 을 중지시키고
  				e.preventDefault();
  				//중복 체크 먼저 요청한다.
  				alert("먼저 아이디 중복체크를 완료해주세요");
  			}
  		});
  	});
  </script>
  <style>
  	#loginForm, #box_check{
  		display:none;
  	}
  </style>
</head>
<body>
	<h2>회원가입</h2>
	<hr>
	<div id="box_email">
		 이메일 : <input type="email" name="email" id="email">
		 <button id="btnSendEmail">인증코드 전송</button>
	 </div>
	 <div id="box_check">
		 인증번호 : <input type="text" id="checkNum">
		 <button id="btnCheckEmail">입력</button>
	 </div>
	
	<form action="join" method="post" id="loginForm">
		 아이디 : <input type="text" name="id" id="id"><button id="btnIsUnike">중복체크</button><br>
		 비밀번호 : <input type="password" name="pwd"><br>
		 
		 이름 : <input type="text" name="m_name"><br>
		 이메일 : <input type="email" name="email" id="emailOK" readOnly><br>
		 휴대폰 : <input type="text" name="phone"><br>
		 
		 <input type="reset" value="지우기">
		 <input type="submit" value="등록" id="btnSubmit">
	</form>
</body>
</html>
  • listBoard.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h2>게시물 목록</h2>
	<hr>
	<a href="insertBoard">게시물 등록</a><br>
	<table border="1" width="80%">
		<tr>
			<th>글번호</th>
			<th>글제목</th>
			<th>작성자</th>
		</tr>
		
		<c:forEach var="b" items="${list }">
			<tr>	
				<td>${b.no }</td>
				<td>
					<%-- b_level만큼 들여쓰기를 해주고 글 앞에 ㄴ을 붙여준다. --%>
					<c:if test="${b.b_level > 0}">
						
						<c:forEach begin ="1" end = "${b.b_level}">
						&nbsp;&nbsp;
						</c:forEach>
						<span style="color:#b7b7b7">ㄴ </span>
					</c:if>
					<a href="detailBoard?no=${b.no }">${b.title }</a>
				</td>
				<td>${b.writer }</td>
			</tr>
		</c:forEach>
	</table>
</body>
</html>
  • updateBoard.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>
	<h2>게시물 수정</h2>
	<hr>
	<form action="updateBoard" method="post" enctype="multipart/form-data">
		글제목 : <input type="text" name="title" value="${b.title}"><br>
		작성자 : ${b.writer}<br>
		글암호 : <input type="password" name="pwd"><br>
		글내용 : <br>
		<textarea rows="10" cols="80" name="content">${b.content}</textarea><br>
		<input type="hidden" name="fname" value="${b.fname}">
		<input type="hidden" name="fsize" value="${b.fsize}">
		<input type="hidden" name="no" value="${b.no}">
			
		첨부파일 : ${b.fname}(${b.fsize})<br>
		<input type="file" name="uploadFile" ><br>
		<input type="submit" value="수정">
		<input type="reset" value="다시입력">
	</form>
</body>
</html>

 

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--XMl 선언으로 XML 파일의 버전과 인코딩 지정-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<!--project 태그는 Maven 프로젝트의 루트 요소로 이 안에 모든 설정이 포함된다.-->
	<!--xmlns 이 속성은 POM(XML 파일)의 네임스페이스를 지정한다.-->
	<!--xmlns:xsi 이 속성은 XML 스키마 인스턴스 네임스페이스를 지정한다.-->
	<!--xsi:schemaLocation 이 속성은 XML 스키마 파일의 위치를 지정하여 XML 파일이 스키마에 따라 올바르게 구성되었는지 검증한다.-->
	<modelVersion>4.0.0</modelVersion>
	<!--이것은 이 파일의 모델 버전을 지정한다. Maven 2.0 이상에서는 항상 4.0.0 을 사용한다.-->
	<parent>
		<!-- 이 태그는 이 프로젝트가 상속받을 부모 POM 을 정의한다.-->
		<!--  부모 POM은 기본 설정, 종속성 관리 등을 제공하는 상위 프로젝트이다.-->
		<groupId>org.springframework.boot</groupId>
		<!--이 태그는 부모 프로젝트의 그룹 ID를 지정한다. 이는 Spring Boot 프로젝트의 기본 그룹 ID 이다.
		-->
		<artifactId>spring-boot-starter-parent</artifactId>
		<!--부모 프로젝트의 아티팩트 ID지정. 이 부모 POM 은 SPring Boot 프로젝트를 위한 부모 POM 사용-->
		<version>3.3.2</version>
		<!--부모 프로젝트 버전 지정-->
		<relativePath /> <!-- lookup parent from repository -->
		<!--부모 POM 을 로컬에서 찾을 때 사용할 경로를 지정한다. -->
		<!--비워져있으면 Maven 은 기본적으로 중앙 리포지토리 또는 설정된 리포지토리에서 부모 POM 을 찾는다.-->
	</parent>
	<groupId>com.example</groupId>
	<!--프로젝트 그룹 ID를 지정하며, 이 프로젝트를 식별하는 데 사용한다.-->
	<!-- 일반적으로 조징이나 회사의 도메인 이름을 사용한다.-->
	<artifactId>boardtests</artifactId>
	<!--프로젝트 아티팩트 ID를 지정한다. -->
	<!-- 아티팩트 ID는 특정 프로젝트를 식별하는 데 사용되며, 보통 프로젝트의 이름을 나타낸다.-->
	<version>0.0.1-SNAPSHOT</version>
	<!--프로젝트의 버전을 지정한다. 0.0.1-SNAPSHOT은 개발 중인 초기 버전을 의미-->
	<!--아직 이 버전이 릴리즈 되지 않음-->
	<packaging>war</packaging>
	<!--pakaging 은 Maven 프로젝트가 어떻게 패키징될지 지정한다.-->
	<!-- war 로 설정되어있다면 WAR(Web Application Archive)파일로 패키징 된다는 것을 의미한다.-->
	<name>boardtests</name>
	<!--프로젝트 이름 지정-->
	<description>Demo project for Spring Boot</description>
	<!--프로젝트에 간단한 설명을 제공-->
	<url />
	<!-- 프로젝트의 URL 을 지정하며, 보통 프로젝트 홈페이지나 관련 정보를 제공한ㄴ 웹사이트 URL 을 입력-->
	<licenses>
		<!--프로젝트에서 사용하는 라이센스 정리-->
		<license />
	</licenses>
	<developers>
		<!--프로젝트의 개발자 정보를 지정함.-->
		<developer />
	</developers>
	<scm>
		<!--소스 코드 관리(SCM) 시스템에 대한 정보를 지정-->
		<connection />
		<!--익명 사용자가 소스 코드에 접근할 수 있는 URL 을 지정-->
		<developerConnection />
		<!-- 개발자가 소스 코드에 접근할 수 있는 URL 을 지정-->
		<tag />
		<!-- 소스 코드의 특정 태그를 지정-->
		<url />
		<!-- 소스 코드가 호스팅된 URL 을 지정-->
	</scm>
	<properties>
		<!--Maven 빌드에서 사용할 사용자 정의 속성을 정의-->
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<!-- 프로젝트에서 사용할 외부 라이브러리를 지정하는 부분-->
		<dependency>
			<!--Spring boot 웹 애플리케이션을 개발하는 데 필요한 기본적인 라이브러리들을 포함하는 것을 추가-->
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!--Oracle DB 연결을 위한 JDBC 드라이버 ojdbc11을 추가-->
		<!--범위는 runtime 이므로 애플리케이션 실행 시에만 포함-->
		<dependency>
			<groupId>com.oracle.database.jdbc</groupId>
			<artifactId>ojdbc11</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!--lombok 라이브러리 추가-->
		<!-- optional 속성이 true 이므로 선택적인 의존성-->
		<!-- 선택적 의존성 : 프로젝트에서는 필요하지만 이 프로젝트를 의존성으로 사용하는 다른 프로젝트에서는 필수적이지 않음을
		뜻함-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!--Tomcat 서블릿 컨테이너를 포함함-->
		<!-- provided 가 범위로 되어있어, 배포 패키지에 포함되지 않음-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<!--이 의존성은 Spring Boot에서 테스트를 지원하는 라이브러리들을 포함하는 spring-boot-starter-test를 추가-->
		<!-- test 범위로 지정되어 있어, 테스트 시에만 포함-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!--이 의존성은 MyBatis 라이브러리 mybatis를 추가-->
		<!--MyBatis는 SQL 매핑 프레임워크로, 데이터베이스와의 상호작용을 간소화-->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.15</version>
		</dependency>

		<!--이 의존성은 JSP(JavaServer Pages)를 지원하기 위한 Tomcat Jasper를 추가-->
		<!-- provided 가 범위로 되어있어, 배포 패키지에 포함되지 않음-->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
			<scope>provided</scope>
		</dependency>
		<!--JSP 의 JSTL 사용할 수 있게 해줌-->
		<dependency>
			<groupId>org.glassfish.web</groupId>
			<artifactId>jakarta.servlet.jsp.jstl</artifactId>
			<version>2.0.0</version>
		</dependency>
		<!--서블릿  API 을 추가-->
		<!-- provided 가 범위로 되어있어, 배포 패키지에 포함되지 않음-->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>

		<!--이 의존성은 Apache Commons FileUpload 라이브러리를 추가하여 파일 업로드 기능을 제공-->
		<dependency>
			<groupId>commons-fileupload</groupId>
			<artifactId>commons-fileupload</artifactId>
			<version>1.5</version>
		</dependency>

		<!-- 이 의존성은 Apache Commons IO 라이브러리를 추가하여 다양한 IO 기능을 제공 -->
		<dependency>
			<groupId>commons-io</groupId>
			<artifactId>commons-io</artifactId>
			<version>2.15.1</version>
		</dependency>

		<!-- 메일발송 지원 기능 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
			<version>5.3.24</version>
		</dependency>

		<!-- Java Mail API -->
		<dependency>
			<groupId>javax.mail</groupId>
			<artifactId>mail</artifactId>
			<version>1.4.7</version>
		</dependency>

	</dependencies>

	<!--프로젝트 빌드 관련 설정 정의-->
	<build>
		<!--Maven 빌드에서 사용할 플러그인을 정의-->
		<plugins>
			<!--Spring Boot 애플리케이션을 빌드하고 패키징하기 위한 것을 추가-->
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<!--플러그인의 구성을 정의-->
				<configuration>
					<!-- lombok을 빌드 시 제외하는 설정이 있다.-->
					<excludes>
						<exclude>
							<groupId>org.projectlombok</groupId>
							<artifactId>lombok</artifactId>
						</exclude>
					</excludes>
				</configuration>
			</plugin>
		</plugins>
	</build>

</project>

'Kosta DevOps 과정 280기 > Java' 카테고리의 다른 글

Wrapper 클래스  (0) 2024.08.08
MVC 패턴과 DI  (0) 2024.08.01
어노테이션 기반의 DI  (0) 2024.08.01
DI와 XML 이용-2  (0) 2024.08.01
어제 복습  (0) 2024.08.01