본문 바로가기
개발 공부 Today I Learned

[국비 74일차 TIL] 관리자 메뉴, 카테고리 추가, 삭제, 수정, AOP

by 개발자신입 2024. 3. 11.
반응형

에러 페이지

ErrorController.java

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;

@Controller
public class ErrorController {

	@GetMapping("/error")
	public String error() {
		
		return "error";
	}
}

error.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1>ERROR</h1>
	문제 발생
	code : [[${code}]]
</body>
</html>

로그인 한 사람으로 사용자 제한

IndexService.java

	public int write(Map<String, Object> map) {
		// DB에 있는 mid, ip 추가하기
//		map.put("mtcate", map.get("cate"));

		map.put("mid", util.getSession().getAttribute("mid")); // 로그인 한 사용자의 mid
		map.put("ip", util.getIP());
		
		return indexDAO.write(map);
	}
}

IndexController.java

	@GetMapping("/write")
	public String write() {
		return "redirect:/login?error=2077"; // 로그인 안 한 사람이 write치고 들어오면 에러 페이지로
	}
	
	@PostMapping("/write")
	public String write(@RequestParam Map<String, Object> map, HttpServletRequest request) {
//		String ip = util.getIP();
		
		// 2024-03-11 로그인 검사
		if(util.getSession().getAttribute("mid") != null) {
			int result = indexService.write(map);
			return "write";
		} else {
			return "redirect:/login";
		}
	}

		// 이렇게 작성도 가능함.
		String url = "freeboard";
		return "redirect:/" + url;

board.html

로그인 한 사람만 글 쓰도록 th:if="${session.mid ne null}"

		<div th:if="${session.mid ne null}">
			<button type="button" th:with="cate=${board[0].mtcate}" class="btn btn-dark" th:onclick="|location.href='@{/write(cate=${cate})}'|">글쓰기</button>
			
			<button type="button" class="btn btn-dark" th:onclick="|location.href='@{/write(cate=${param.cate})}'|">글쓰기</button>
		</div>

board.html 전체 코드

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:insert="~{menu.html :: head}" />
</head>
<body id="page-top">
	<!-- Navigation-->
	<th:block th:insert="~{menu.html :: menu}" />
	<br>
	<br>
		<!-- 2024-03-08  -->
		<aside class="text-center">
			<div class="container px-5">

				<span>[[${#lists.size(board)}]]개 글이 있습니다</span>
				<div th:if="${#lists.size(board) le 0}">
					<h2>출력할 데이터가 없습니다.</h2>
					<h3>관리자에게 문의하세요.</h3>
				</div>
				<div th:unless="${#lists.size(board) le 0}">
					<div class="table table-hover">
						<div class="row" th:each="row : ${board }">
							<div class="col-1" th:text="${row.mtno }"></div>
							<div class="col-6 text-start" th:onclick="|location.href='@{/detail(no=${row.mtno})}'|">[[${row.mttitle }]]</div>
							<div class="col-2" th:text="${row.mname }"></div>
							<div class="col-2" th:text="${row.mtdate }"></div>
							<div class="col-1" th:text="${row.mtread }"></div>
						</div>
					</div>
					</div>
					<div th:if="${session.mid ne null}">
						<button type="button" th:with="cate=${board[0].mtcate}" class="btn btn-dark" th:onclick="|location.href='@{/write(cate=${cate})}'|">글쓰기</button>
						<button type="button" class="btn btn-dark" th:onclick="|location.href='@{/write(cate=${param.cate})}'|">글쓰기</button>
					</div>
				</div>
				</aside>
				<!-- footer -->
				<th:block th:insert="~{menu.html :: footer}" />

</body>
</html>

menu.html

                <th:block th:if="${session.mid ne null}">
                    <li class="nav-item"><a class="nav-link me-lg-3" href="/myInfo">[[${session.mname}]]</a></li>
                    <li class="nav-item"><a class="nav-link me-lg-3" href="/logout">LOGOUT</a></li>
                </th:block>
                <th:block th:unless="${session.mid ne null}">
                    <li class="nav-item"><a class="nav-link me-lg-3" href="/login">LOGIN</a></li>
                </th:block>

메뉴 만들기

AdminController.java

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/admin")
public class AdminController {

	@GetMapping("/menu")
	public String menu() {
		return "admin/menu";
	}
}

menu.html (admin)

* 경로 :  templates/admin (폴더 생성)/menu.html

 

DB 새로 생성 

CREATE TABLE `menu` (
	`cate` INT(11) NULL DEFAULT NULL,
	`catename` VARCHAR(50) NULL DEFAULT NULL COLLATE 'utf8mb4_general_ci',
	`comment` VARCHAR(100) NULL DEFAULT NULL COLLATE 'utf8mb4_general_ci',
	UNIQUE INDEX `cate` (`cate`) USING BTREE
)
COMMENT='화면 상단에 보여줄 메뉴\r\n멀티보드를 활용해서 mtcate 값만 바꿔주면 됨.\r\ncate = unique (카테고리 중복 피하도록, 겹치지 않음)'
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
;

AdminController

@Controller
@RequestMapping("/admin")
public class AdminController {

	@Autowired
	private AdminService adminService;

 AdminService

@Service
public class AdminService {

	@Autowired
	private AdminDAO adminDAO;
}

AdminDAO (인터페이스)

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface AdminDAO {
}

AdminController

	@GetMapping("/menu")
	public String menu(Model model) {
		List<Map<String, Object>> menu = adminService.menu();
		model.addAttribute("menu",menu);
		return "admin/menu";
	}

AdminService

@Service
public class AdminService {

	@Autowired
	private AdminDAO adminDAO;

	public List<Map<String, Object>> menu() {
		return adminDAO.menu();
	}
}

AdminDAO (인터페이스)

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface AdminDAO {

	List<Map<String, Object>> menu();

}

adminMapper.xml

아까 만들어준 menu 테이블의 속성 SELECT 하기.

<mapper namespace="com.example.web.dao.AdminDAO">
	<select id="menu" resultType="Map">
 		SELECT cate, catename, comment
 		FROM menu
	</select>
</mapper>

IndexController

메뉴도 불러오고, 보드도 불러옴

	@GetMapping({ "/index", "/" })
	public String index(Model model) {
		// 메뉴 불러오기
		List<Map<String, String>> menu = indexService.menu();
		model.addAttribute("menu", menu);
		return "index";
	}
    
    @GetMapping("/freeboard")
	public String freeboard(@RequestParam(value="cate", defaultValue="1") int cate, Model model) {

		// 메뉴 불러오기
		List<Map<String, Object>> menu = indexService.menu();
		model.addAttribute("menu", menu);
		
		List<BoardDTO> board = indexService.freeboard(cate);
		model.addAttribute("board", board);
		return "board";
	}

IndexService

	public List<Map<String, Object>> menu() {
		return indexDAO.menu();
	}

IndexDAO

	List<Map<String, Object>> menu();

indexMapper.xml

	<select id="menu" resultType="Map">
		SELECT cate, catename
		FROM menu
	</select>

관리자 메뉴 페이지

Admin/menu.html

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>admin MENU</title>
</head>
<body>
	<h1>관리자 메뉴페이지~</h1>
	
	<table border="1">
		<tr>
			<th>카테고리 번호</th>
			<th>카테고리 이름</th>
			<th>비고</th>
		</tr>
		<tr th:each="m : ${menu}">
			<td th:text="${m.cate}"></td>
			<td th:text="${m.catename}"></td>
			<td th:text="${m.comment}"></td>
		</tr>
	</table>
	<br>
	<form action="menu" method="post">
		<input type="number" name="cate" placeholder="카테고리 번호">
		<input type="text" name="catename" placeholder="카테고리 이름">
		<input type="text" name="comment" placeholder="카테고리 설명">
		<button type="submit">추가하기</button>
	</form>
</body>
</html>

AdminController

	@PostMapping("/menu")
	public String menu(@RequestParam Map<String, Object> map) {
//		System.out.println(map);
		
		adminService.menuInsert(map);
		return "redirect:/admin/menu";
	}
}

 

-> menuInsert 생성 (서비스, DAO)

AdminService

	public void menuInsert(Map<String, Object> map) {
		adminDAO.menuInsert(map);
	}

AdminDAO

void menuInsert(Map<String, Object> map);

adminMapper

mapper 작성 후 메뉴 추가 가능

	<insert id="menuInsert" parameterType="Map">
		INSERT INTO menu VALUES(#{cate}, #{catename}, #{comment})
	</insert>

IndexController

	@GetMapping({"/index", "/"})
	public String index(Model model) {
		List<Map<String, Object>> menu = indexService.menu();
		model.addAttribute("menu", menu);
		return "index";
	}

application.properties

# session time out = 1800 -> 30분
server.servlet.session.timeout=1800

#### error page

# error page에 exception 정보 포함? true/false
server.error.include-exception=true

# error page에 stacktrace 포함? ALWAYS / NEVER / ON_PARAM
server.error.include-stacktrace=ALWAYS

# 기본 노출 페이지
server.error.whitelabel.enabled=true

# error 응답 처리 할 path
# server.error.path=/error

삭제하기 postDel

IndexController

	@PostMapping("/postDel")
	public String postDel(@RequestParam("no") int no) {
//		System.out.println(no);       
		int result = indexService.postDel(no);
		return "redirect:/freeboard";
	}

IndexService

	public int postDel(int no) {
		return indexDAO.postDel(no);
	}

IndexDAO

	int postDel(int no);

indexMapper

	<update id="postDel" parameterType="int">
		UPDATE multiboard SET mtdel=0
		WHERE mtno=#{no}
	</update>

detail.html

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>

<script type="text/javascript">
        function del(no){
      	  Swal.fire({
      		  title: "삭제합니까?",
      		  text: "해당 내용을 삭제합니다. 복구가 불가능합니다.",
      		  icon: "warning",
      		  showCancelButton: true,
      		  confirmButtonColor: "#3085d6",
      		  cancelButtonColor: "#d33",
      		  confirmButtonText: "삭제"
      		}).then((result) => {
      		  if (result.isConfirmed) {
      		    //Swal.fire({title: "삭제를 선택했습니다.",text: "삭제합니다.",icon: "success"});
      		    //location.href="/postDel?no="+no;
      		    let form = document.createElement('form');
      		    form.setAttribute('method','post');
      		    form.setAttribute('action','/postDel');
      		    
      		    let input = document.createElement('input');
      		    input.setAttribute('type','hidden');
      		    input.setAttribute('name','no');
      		    input.setAttribute('value', no);
      		    
      		    form.appendChild(input);
      		    document.body.appendChild(form);
      		    form.submit();
      		  }
      		});
        }
</script>
</head>
<body id="page-top">
	<!-- Navigation-->
	<th:block th:insert="~{menu.html :: menu}"></th:block>
	<!-- Mashead header-->

	<!-- Quote/testimonial aside-->
	<aside class="text-center">
		<div class="container px-5">

			<div class="p-3 mt-5 mb-2 rounded" style="background-color: #FAFAFA">
				<div class="border-bottom">
					<h3 th:text="${detail.mttitle}"></h3>
				</div>
				<div class="border-bottom" style="background-color: #c0c0c0">
					<div class="row">
						<div class="col-6 text-start">
							[[${detail.mname }]]님 
							<i class="bi bi-pencil-fill" th:id="${detail.mtno }" onclick="update(this.id)"></i> 
							<i class="bi bi-trash2-fill" th:id="${detail.mtno }" onclick="del(this.id)"></i>
						</div>
						<div class="col-6 row text-end">
							<div class="col-7" th:text="${detail.mtread }"></div>
							<div class="col-5" th:text="${detail.mtip }"></div>
						</div>
					</div>
				</div>
				<div class="mt-3 text-start" th:utext="${detail.mtcontent }" style="min-height: 300px; height: auto"></div>
			</div>

			<a href="/index" class="btn">게시판으로</a>
		</div>
	</aside>

indexMapper.xml

	<update id="postDel" parameterType="int">
		UPDATE multiboard SET mtdel=0
		WHERE mtno=#{no}
	</update>

수정하기 UPDATE

update.html

th:value="${update.mttitle}"

textarea에 [[ ${update.mtcontent} ]]  추가

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:insert="~{menu.html :: head}" />
</head>
<body>
	<th:block th:insert="~{menu.html :: menu}" />
	<br>
	<br>

	<aside class="text-center">
		<div class="container px-5">
			<br> <br>
			<h1>글 수정하기</h1>
			<!-- 카테고리, 글번호, 페이지번호 -->
			들어온 카테고리 : [[${param.cate}]]<br> [[${update}]]

			<form action="/postUpdate" method="post">
				<div class="form-group col-md-6 d-flex align-items-center justify-content-center">
					<input type="text" id="title" name="title" class="form-control" th:value="${update.mttitle}" placeholder="제목" aria-label="제목">
				</div>
				<div class="form-group col-md-6 d-flex align-items-center justify-content-center">
					<textarea id="content" name="content" class="form-control" aria-label="With textarea" style="height: 300px;">[[${update.mtcontent}]]</textarea>
				</div>
				<div class="form-group col-md-12">
					<input type="hidden" name="mtno" th:value="${update.mtno}"> <input type="hidden" name="mtcate" th:value="${update.mtcate}">

					<button type="submit" class="btn btn-dark">글쓰기</button>
				</div>
			</form>
			<input type="hidden" name="mtcate" th:value="${param.cate}">
		</div>
	</aside>

BoardDTO 삭제 후 내용들 변경

-> mapper에서 BoardDTO를 Map으로 변경

-> IndexController, IndexService, IndexDAO : DTO 자리에 **Map<String, Object>** 으로 교체

	@PostMapping("/postUpdate")
	public String postUpdate(@RequestParam("no") int no, Model model) {
		
		if(util.getSession().getAttribute("mid") != null) {
			// 메뉴 불러오기
			List<Map<String, Object>> menu = indexService.menu();
			model.addAttribute("menu", menu);

			Map<String, Object> update2 = indexService.detail(no);
			model.addAttribute("update", update2);
			return "update";
		} else {
			return "redirect:/login";
		}
	}
	
	@PostMapping("/postUpdate")
	public String postUpdate(@RequestParam() Map<String, Object> map) {
		indexService.postUpdate(map);
		return "redirect:/detail?no="+ map.get("mtno");
	}
}

IndexService.java

	public void postUpdate(Map<String, Object> map) {
		map.put("mid", util.getSession().getAttribute("mid"));// 본인 맞는지
		indexDAO.postUpdate(map);
	}

IndexDAO

	void postUpdate(Map<String, Object> map);

indexMapper.xml

	<update id="postUpdate" parameterType="Map">
		UPDATE multiboard SET mttitle=#{title}, mtcontent=#{content}
		WHERE mtno=#{mtno} AND mno=(SELECT mno FROM member WHERE mid=#{mid})
	</update>

detail.html

[[${detail.mname }]]님 
<i class="bi bi-pencil-fill" th:id="${detail.mtno }" onclick="update(this.id)"> 
<i class="bi bi-trash2-fill" th:id="${detail.mtno }" onclick="del(this.id)">

 

<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>

	<script type="text/javascript">
        function update(no){
      	  Swal.fire({
      		  title: "수정합니까?",
      		  text: "해당 내용을 수정합니다.",
      		  icon: "warning",
      		  showCancelButton: true,
      		  confirmButtonColor: "#3085d6",
      		  cancelButtonColor: "#d33",
      		  confirmButtonText: "수정"
      		}).then((result) => {
      		  if (result.isConfirmed) {
      			let form = document.createElement('form');
        		    form.setAttribute('method','get');
        		    form.setAttribute('action','/postUpdate');
        		    
        		    let input = document.createElement('input');
        		    input.setAttribute('type','hidden');
        		    input.setAttribute('name','no');
        		    input.setAttribute('value', no);
        		    
        		    form.appendChild(input);
        		    document.body.appendChild(form);
        		    form.submit();
      		  }
      		});
        }

	<!-- Quote/testimonial aside-->
	<aside class="text-center">
		<div class="container px-5">

			<div class="p-3 mt-5 mb-2 rounded" style="background-color: #FAFAFA">
				<div class="border-bottom">
					<h3 th:text="${detail.mttitle}"></h3>
				</div>
				<div class="border-bottom" style="background-color: #c0c0c0">
					<div class="row">
						<div class="col-6 text-start">
							[[${detail.mname }]]님 
							<i class="bi bi-pencil-fill" th:id="${detail.mtno }" onclick="update(this.id)"></i> 
							<i class="bi bi-trash2-fill" th:id="${detail.mtno }" onclick="del(this.id)"></i>
						</div>
						<div class="col-6 row text-end">
							<div class="col-7" th:text="${detail.mtread }"></div>
							<div class="col-5" th:text="${detail.mtip }"></div>
						</div>
					</div>
				</div>
				<div class="mt-3 text-start" th:utext="${detail.mtcontent }" style="min-height: 300px; height: auto"></div>
			</div>

			<a href="/index" class="btn">게시판으로</a>
		</div>
	</aside>

freeboard 수정

IndexController

List<Map<String, String>> menu = indexService.menu();

Map에서 String, String임. (서비스, DAO에서도 String, String으로 바꿔야 함.)

	@GetMapping("/freeboard")
	public String freeboard(@RequestParam(value="cate", defaultValue="1") int cate, Model model) {

		// 메뉴 불러오기
		List<Map<String, String>> menu = indexService.menu();
		model.addAttribute("menu", menu);
		
		List<Map<String, Object>> board = indexService.freeboard(cate);
		model.addAttribute("board", board);
		return "board";
	}

IndexService

변수 없는 freeboard는 삭제 (dao에서도)

	public List<Map<String, Object>> freeboard(int cate) {
		return indexDAO.freeboard(cate);
	}
	
	public List<Map<String, String>> menu() {
		return indexDAO.menu();
	}

IndexDAO

	List<Map<String, Object>> freeboard(int cate);
	List<Map<String, String>> menu();

로그인 안됐던 이유

MemberController

추가 내용 : 

  • @Autowired : private IndexService indexService;
  • @getmapping("/login")의 로그인 매개변수에 모델 추가 = login(Model model)
  • @postmapping에서 map으로 바꿨던 걸 result 로 변경함.
import java.util.List;
import java.util.Map;

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.RequestParam;

import com.example.web.service.IndexService;
import com.example.web.service.MemberService;
import com.example.web.util.Util;

import jakarta.servlet.http.HttpSession;

@Controller
public class MemberController {

	@Autowired
	private MemberService memberService;

	@Autowired
	private IndexService indexService;

	@Autowired
	private Util util;

	@GetMapping("/login") // 화면만 보여줌
	public String login(Model model) {
		// 메뉴 불러오기
		List<Map<String, String>> menu = indexService.menu();
		model.addAttribute("menu", menu);
		return "login";
	}

	@PostMapping("/login") // 실제 로그인 작업
	public String login2(@RequestParam Map<String, Object> map) {
		System.out.println(map); // 콘솔 출력 : {id=test, pw=01234567}
		Map<String, Object> result = memberService.login(map); // 받아노는 값이 하나가 아니니까 map 에 넣어줘
		System.out.println(result);
		
		if (util.str2Int(result.get("count")) == 1) { // mapper에서 오는 count(*) 의 별칭
			// 정상 로그인 -> 세션 -> board 이동
			HttpSession session = util.getSession();
			session.setAttribute("mid", map.get("id"));
			session.setAttribute("mname", result.get("mname"));
			return "redirect:/freeboard";
		} else {
			// 로그인 불가
			return "redirect:/login";
		}
	}

	@GetMapping("/logout")
	public String logout(HttpSession session) {
		if (session.getAttribute("mid") != null) {
			session.removeAttribute("mid");
		}
		if (session.getAttribute("mname") != null) {
			session.removeAttribute("mname");
		}
		session.invalidate();
		return "redirect:/login";
	}
}

AOP

- AOP (Aspect-Oriented Programming) = 관점 지향 프로그래밍

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-aop'
-> 코드 추가 후 gradle update

AOPConfig

@Aspect = 어드바이스(Advice), 포인트컷(Pointcut), 및 인트로덕션(Introduction) 등을 정의 가능

package com.example.web.util;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;

@Aspect // 어드바이스(Advice), 포인트컷(Pointcut), 및 인트로덕션(Introduction) 등을 정의 가능
@Component
public class AOPConfig {

	@Pointcut("execution(* com.example.web.controller.*.*(..))")
	public void cut() {
		// 포인트컷 표현식이 필요한 경우 여기에 정의
	}

	@Before("cut()")
	public void before(JoinPoint joinPoint) {
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

		System.out.println("시작할 때 : " + methodSignature.getName()); // 실행 메소드명
		System.out.println("시작할 때 : " + methodSignature.getMethod());

		// 파라미터
		Object[] args = joinPoint.getArgs();
		System.out.println(Arrays.toString(args));

		// 파라미터 배열의 종류 값
		for (Object object : args) {
			System.out.println("파라미터 타입 : " + object.getClass().getSimpleName());
			System.out.println("파라미터 값 : " + object);
		}
	}

	@After("cut()")
	public void after(JoinPoint joinPoint) {
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		// 실행되는 함수 이름을 가져오고 출력
		System.out.println(methodSignature.getName() + " 메소드가 종료됨");
	}
}

 

반응형

댓글