본문 바로가기

SpringBoot

SpringBoot(25) - 복습 예제(글쓰기)

728x90
반응형

1) SpringBoot

   1-1) 복습 예제

      1-1-1) 글쓰기 기능 구현 

 

 

 

 

 

1) SpringBoot

1-1) 복습 예제

1-1-1) 글쓰기 기능 구현

(1) [src/main/resources] - [templates] 안에 home.html 파일 생성 후 아래와 같이 작성

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org"
		xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>web5</title>
</head>
<body>
	<h1>[ web5 ]</h1>
	<p sec:authorize="isAuthenticated()">
		<span th:text="${#authentication.name}"></span>
		님 환영합니다~~!
	</p>
	
	<div sec:authorize="not isAuthenticated()">
		<p>
			<a th:href="@{/member/joinForm}">회원가입</a>
		</p>
		<p>
			<a th:href="@{/member/loginForm}">로그인</a>
		</p>
	</div>
	
	<div sec:authorize="isAuthenticated()">
		<p>
			<a th:href="@{/member/logout}">로그아웃</a>
		</p>
		<p>
			<a th:href="@{/member/info}">개인정보 수정</a>
		</p>
	</div>
	
	<p>
		<a th:href="@{/board/list}">게시판</a>
	</p>
	
</body>
</html>

 

 

(2) [src/main/resources] - [templates.boardView] 안에 list.html 파일 생성 후 아래와 같이 작성

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org"
	xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>게시판</title>
</head>
<body>
	<h1>[ 게시판 ]</h1>
	
	<p>
		<a th:href="@{/board/write}">글쓰기</a>
	</p>
	
</body>
</html>

 

 

(3) [src/main/resources] - [templates.boardView] 안에 writeForm.html 파일 생성 후 아래와 같이 작성

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org"
	xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
	<meta charset="UTF-8">
	<title>글쓰기</title>
	<style>
		#writeArea {
			width: 900px;
			margin: 0 auto;
			text-align: center;
		}
		
		#writeForm {
			display: flex;
			flex-direction: column;
			justify-content: center;
		}
			
		#formArea {
			display: flex;
			justify-content: center;
		}
		
		table, tr, td {
			border: 2px solid black;
        	border-collapse: collapse;
        	padding: 10px;
        	margin: 20px 0 15px 0;
		}
		
		th {
			width: 100px;
			background-color: grey;
			color: white;
		}
		
		td {
			width: 300px;
			text-align: left;
		}
		
		td > .writeContent {
			width: 300px;
		}
		
		td > textarea {
			height: 250px;
		}
		
	</style>
	<script th:src="@{/js/jquery-3.7.1.min.js}"></script>
	<script>
	$(document).ready(function() {
		$("#writeForm").submit(check);
	});
	
	function check() {
		let title = $("#title").val();
		let contents = $("#contents").val();
		
		if (title.length < 7) {
			alert("제목은 최소 7자 이상으로 반드시 입력해주세요!!");
			$("#title").focus();
			$("#title").val('');
			
	        return false;
		}
		
		if (contents.length < 20) {
			alert("내용은 20자 이상으로 반드시 입력해주세요!!");
			$("#contents").focus();
			$("#contents").val('');
			
	        return false;
		}
		
		return true;
		
	}
	
	</script>
</head>
<body>
	<div id="writeArea">
		<h1>[ 글쓰기 ]</h1>
		
		<form th:action="@{/board/write}" method="post" id="writeForm">
			<div id="formArea">
				<table>
					<tr>
						<th>
							<label for="title">제목</label>
						</th>
						<td>
							<input type="text" name="title" id="title" class="writeContent" />
						</td>
					</tr>
					<tr>
						<th>
							<label for="contents">내용</label>
						</th>
						<td>
							<textarea name="contents" id="contents" class="writeContent"></textarea>
						</td>
					</tr>
					<tr>
						<th>파일첨부</th>
						<td>
							<input type="file" name="file" id="file" class="writeContent" />
						</td>
					</tr>
				</table>
			</div>
			
	        <div id="btnArea">
	        	<input type="submit" value="저장" />
	        </div>
		</form>
	</div>
</body>
</html>

 

 

(4) [src/main/java] - [net.datasa.web5.security] 안에 WebSecurityConfig.java 파일 생성 후 아래와 같이 작성

package net.datasa.web5.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    //로그인 없이 접근 가능한 경로
    private static final String[] PUBLIC_URLS = {
            "/"                 //메인화면
            , "/member/joinForm"          //로그인 없이 접근할 수 있는 페이지
            , "/member/join"
            , "/member/idCheck"
            , "/board/list"
            , "/images/**"
            , "/css/**"
            , "/js/**"
    };

    // Bean : 메모리에 만들어진 객체(즉, 객체를 생성해서 메모리에 로드시켜 놓는 역할을 한다!)
    @Bean
    protected SecurityFilterChain config(HttpSecurity http) throws Exception {
        http
            //요청에 대한 권한 설정
            .authorizeHttpRequests(author -> author
                .requestMatchers(PUBLIC_URLS).permitAll()   //모두 접근 허용
                // 모든 요청은 인증이 필요하다는 코드이며, 바로 위의 코드만 인증 없이 모두 접근 가능하다!
                .anyRequest().authenticated()               //그 외의 모든 요청은 인증 필요
            )
            //HTTP Basic 인증을 사용하도록 설정
            .httpBasic(Customizer.withDefaults())
            //폼 로그인 설정
            .formLogin(formLogin -> formLogin
                    .loginPage("/member/loginForm")              //로그인폼 페이지 경로
                    .usernameParameter("memberId")               //폼의 ID 파라미터 이름
                    .passwordParameter("memberPassword")         //폼의 비밀번호 파라미터 이름
                    .loginProcessingUrl("/member/login")         //로그인폼 제출하여 처리할 경로
                    .defaultSuccessUrl("/")                      //로그인 성공 시 이동할 경로
                    .permitAll()                                 //로그인 페이지는 모두 접근 허용
            )
            //로그아웃 설정
            .logout(logout -> logout
                    .logoutUrl("/member/logout")                 //로그아웃 처리 경로
                    .logoutSuccessUrl("/")                       //로그아웃 성공 시 이동할 경로
            );

        http
            .cors(AbstractHttpConfigurer::disable)
            .csrf(AbstractHttpConfigurer::disable);

        return http.build();
    }

    //비밀번호 암호화를 위한 인코더를 빈으로 등록
    @Bean
    public BCryptPasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

 

 

(5) [src/main/java] - [net.datasa.web5] 안에 Web5Application.java 파일 생성 후 아래와 같이 작성

package net.datasa.web5;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class Web5Application {

	public static void main(String[] args) {
		SpringApplication.run(Web5Application.class, args);
	}

}

 

 

(6) DB에 "web5_board" Table 생성을 위해 아래와 같이 작성

-- DB 구조
-- 게시판 글(테이블명 : web5_board)

create table web5_board(
--  컬럼명              자료형            제약조건           						설명
	board_num         int            auto_increment primary key,    		-- 게시글 일련번호  
	member_id         varchar(30),       						            -- 작성자 ID(외래키 : web5_member 테이블 참조)      
	title       	  varchar(1000)  not null,       						-- 글제목     
	contents          text           not null,                   			-- 글내용  
	view_count        int            default 0,                   			-- 조회수
	like_count        int            default 0,                  			-- 추천수
	original_name     varchar(300),                                         -- 첨부파일 원래 이름
	file_name         varchar(100),                                         -- 첨부파일 저장된 이름
	create_date       timestamp      default current_timestamp,             -- 작성 시간
	update_date       timestamp      default current_timestamp
										on update current_timestamp,        -- 수정 시간
	constraint foreign key (member_id)
    			references web5_member(member_id) on delete set null
);

select * from web5_board;

insert into web5_board(title, contents, file_name) values('test1', 'test_content1', 'test_file1');

-- delete from web5_member where member_name = '김종국';

drop table web5_board;

commit;

 

 

(7) [src/main/java] - [net.datasa.web5.domain.entity] 안에 BoardEntity.java 파일 생성 후 아래와 같이 작성

- 날짜 관련 Annotation 체크사항 : Web5Application.java 파일의 "@EnableJpaAuditing", BoardEntity.java 파일의 "@EntityListeners(AuditingEntityListener.class)" / "@CreatedDate" 추가했는지 확인할 것!!

package net.datasa.web5.domain.entity;

import java.time.LocalDateTime;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name="web5_board")
@EntityListeners(AuditingEntityListener.class)
public class BoardEntity {
	
	/*
	board_num         int            auto_increment primary key,    		-- 게시글 일련번호  
	member_id         varchar(30),       						            -- 작성자 ID(외래키 : web5_member 테이블 참조)      
	title       	  varchar(1000)  not null,       						-- 글제목     
	contents          text           not null,                   			-- 글내용  
	view_count        int            default 0,                   			-- 조회수
	like_count        int            default 0,                  			-- 추천수
	original_name     varchar(300),                                         -- 첨부파일 원래 이름
	file_name         varchar(100),                                         -- 첨부파일 저장된 이름
	create_date       timestamp      default current_timestamp,             -- 작성 시간
	update_date       timestamp      default current_timestamp
										on update current_timestamp,        -- 수정 시간
	constraint foreign key (member_id)
    			references web5_member(member_id) on delete set null
	*/
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name="board_num")
	private Integer boardNum;
	
	@Column(name="member_id", length = 30)
	private String memberId;
	
	@Column(name="title", nullable = false, length = 1000)
	private String title;
	
	@Column(name="contents", nullable = false, columnDefinition = "text")
	private String contents;
	
	@Column(name="view_count", columnDefinition = "int default 0")
	private Integer viewCount;
	
	@Column(name="like_count", columnDefinition = "int default 0")
	private Integer likeCount;
	
	@Column(name="original_name", length = 300)
	private String originalName;
	
	@Column(name="file_name", length = 100)
	private String fileName;
	
	// 작성 시간
	@CreatedDate
	@Column(name="create_date", columnDefinition = "timestamp default current_timestamp")
	private LocalDateTime createDate;
	
	// 수정 시간
	@LastModifiedDate
	@Column(name="update_date", columnDefinition = "timestamp default current_timestamp on update current_timestamp")
	private LocalDateTime updateDate;
}

 

 

(8) [src/main/java] - [net.datasa.web5.repository] 안에 BoardRepository.java 파일 생성(반드시 Interface로 생성할 것!!) 후 아래와 같이 작성

package net.datasa.web5.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import net.datasa.web5.domain.entity.BoardEntity;

@Repository
public interface BoardRepository extends JpaRepository<BoardEntity, Integer> {

}

 

 

(9) [src/main/java] - [net.datasa.web5.domain.dto] 안에 BoardDTO.java 파일 생성 후 아래와 같이 작성

package net.datasa.web5.domain.dto;

import java.time.LocalDateTime;

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

/**
 * 게시판 DTO
 * */
@Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BoardDTO {
	Integer boardNum;            // 게시글 일련번호
	String memberId;             // 작성자 아이디(외래키)
	String title;                // 글 제목
	String contents;             // 글 내용
	Integer viewCount;           // 조회수
	Integer likeCount;           // 추천수
	String originalName;         // 첨부파일 원래 이름
	String fileName;             // 첨부파일 저장된 이름
	LocalDateTime createDate ;   // 작성 시간
	LocalDateTime updateDate;    // 수정 시간
}

 

 

(10) [src/main/java] - [net.datasa.web5.controller] 안에 BoardController.java 파일 생성 후 아래와 같이 작성

package net.datasa.web5.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.datasa.web5.domain.dto.BoardDTO;
import net.datasa.web5.security.AuthenticatedUser;
import net.datasa.web5.service.BoardService;

@Slf4j
@RequestMapping("board")
@RequiredArgsConstructor
@Controller
public class BoardController {
	
	private final BoardService boardService;
	
	// 메인화면에서 "board/list" 경로를 클릭했을 때 처리하는 메소드
	// templates/boardView/list.html 파일로 포워딩
	// 로그인 안 한 상태에서 해당 페이지의 "게시판"이라는 제목이 보여야 함
	/**
	 * 글 목록 보기
	 * @return 글 목록 출력 HTML 파일
	 * */
	@GetMapping("list")
	public String list() {
		return "boardView/list";
	}
	
	@GetMapping("write")
	public String write() {
		return "boardView/writeForm";
	}
	
	@PostMapping("write")
	public String join(@AuthenticationPrincipal AuthenticatedUser user,
			@ModelAttribute BoardDTO boardDTO) {
		log.debug("전달된 글정보 : {}", boardDTO);
		
		boardDTO.setMemberId(user.getUsername());
		
		log.debug("로그인한 아이디 추가한 글정보 : {}", boardDTO);
		
		// 서비스로 전달하여 저장
		boardService.saveWrite(boardDTO);
		
		return "redirect:list";
	}
	
}

 

 

(11) [src/main/java] - [net.datasa.web5.service] 안에 BoardService.java 파일 생성 후 아래와 같이 작성

package net.datasa.web5.service;

import org.springframework.stereotype.Service;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.datasa.web5.domain.dto.BoardDTO;
import net.datasa.web5.domain.entity.BoardEntity;
import net.datasa.web5.repository.BoardRepository;

@Slf4j
@RequiredArgsConstructor
@Service
// @Transactional : Commit, Rollback을 감지하는 역할을 하는 Annotation
@Transactional
public class BoardService {
	
	private final BoardRepository boardRepository;

	public void saveWrite(BoardDTO dto) {
		BoardEntity entity = BoardEntity.builder()
				// DTO로 전달받은 값들을 Entity에 세팅
				.memberId(dto.getMemberId())
				.title(dto.getTitle())
				.contents(dto.getContents())
				// 기타 추가 데이터를 Entity에 세팅
				.viewCount(0)
				.likeCount(0)
				.build();
		
		// DB에 저장
		boardRepository.save(entity);
		
	}

}

 

 

(12) 결과 화면

첫 접속 화면 : 게시판 리스트 화면(로그인 안해도 확인 가능) / 글쓰기 기능(로그인한 경우에만 사용 가능함)

 

 

글쓰기 화면

- 글쓰기 전

 

- 글쓰기 후