상세 컨텐츠

본문 제목

프로그래머 도전기 95일차

프로그래머가 될거야!

by Choyee 2024. 1. 4. 22:05

본문

오늘은

 

Spring과 React를 동시에 공부하다보니

재미도 두배인것같습니다

Spring은 기존과 비슷하면서도 훨씬 깊게 들어가는 느낌이고

React는 기존의 것들을 종합해서 새로운 것을 배우는 느낌입니다

파이널 프로젝트 때 이 두가지를 활용하여 코딩을 할 생각을 해보니

흥미진진하면서도 역시나 걱정이 많이 되는 것 같습니다

 

 

 

Spring 공부

 

예외 및 오류 처리
주요 클래스 = ControllerAdvice
                  Advice= 메인X 지원O
- 500(코드 및 구문 오류)

web.xml - 추가 설정
- 404(페이지 없음)

 

package com.khit.web.exception;

import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.NoHandlerFoundException;

@ControllerAdvice  // 오류를 처리하는 주요 클래스
public class GlobalExceptionAdvice {
	
	// 예외 처리(코드 및 구문 오류)
	@ExceptionHandler(Exception.class)
	public String handleException(Exception exception, Model model) {
		model.addAttribute("exception", exception);
		// forward 방식 사용
		return "/exception/global_error";
	}
	
	// 예외 처리(페이지 찾을 수 없음)
	@ExceptionHandler(NoHandlerFoundException.class)
	@ResponseStatus(HttpStatus.NOT_FOUND)
	public String handle404(NoHandlerFoundException ex) {
		return "/exception/error_404";
	}


}

 

 

web.xml

<!-- 404 에러 처리 -->
<init-param>
    <param-name>throwExceptionIfNoHandlerFound</param-name>
    <param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>

 


- 403(권한 에러)
- 200(정상)

=> 아래쪽 근본원인 부분에 hint가 많다

 

 


정규표현식
-> 비밀번호에 사용될 수 없는 특수문자 존재

 

회원 중복 검사 
- ajax(비동기통신) : 제이쿼리 라이브러리 import필요
- onblur() = 마우스가 떠났을 때(focus가 떠났을 때) 이벤트가 일어남
- $.ajax({
     실행문
   })
- Controller 에서의 반환형 = json 객체
  => @ResponseBody SampleDTO checkUserId( ){ }

const checkUser = function(){
    //alert("가입");
    // 아이디는 4자 이상 15자 이하 까지 입력해주세요
    // 비밀번호는 영문자, 숫자, 특수문자 포함 8자 이상 입력해주세요
    // 이름은 한글로 입력해주세요
    // 나이는 숫자를 입력해주세요
    let form = document.userform//document.getElementById("userform");
    let id = document.getElementById("userId").value;
    let pw = document.getElementById("userPasswd").value;
    let name = document.getElementById("userName").value;
    let age = document.getElementById("userAge").value;

    // 정규 표현식
    let regName = /^[가-힣]+$/;   // 한글만
    let regPw1 = /[0-9]+/;  // 숫자
    let regPw2 = /[a-zA-Z]+/; // 영문자
    let regPw3 = /[~!@#$%^&*()\-=]+/; // 특수문자

    if(id == "" || id.length < 4 || id.length > 15) {
        alert("아이디는 4자 이상 15자 이하 까지 입력해주세요");
        document.getElementById("userId").select();
        return false;
    }else if(pw == "" || pw.length < 8 || !regPw1.test(pw) || !regPw2.test(pw) || !regPw3.test(pw)) {
        alert("비밀번호는 영문자, 숫자, 특수문자 포함 8자 이상 입력해주세요")
        document.getElementById("userPasswd").select();
        return false;
    }else if(name == "" || !regName.test(name)) {// 한글 정규식에 일치하지 않으면
        alert("이름은 한글로 입력해주세요")
        document.getElementById("userName").select();
        return false;
    }else if(age == "" || isNaN(age)) {
        alert("나이는 숫자를 입력해주세요")
        document.getElementById("userAge").select();
        return false;
    }
    else {
        form.submit();   // 유효하면 폼 전송
    }
} // checkUser 닫기

// ID 중복검사 순서
/*
    1. 아이디의 입력 값 가져오기
    2. 입력 값을 서버(controller)에 전송하고 중복된 아이디가 있는지 확인
*/
const checkId = function(){
    //alert("중복검사");
    let userId = document.getElementById("userId").value;
    let checkResult = document.getElementById("check-result");
    console.log(userId);
    if (userId === "") {
        checkResult.innerHTML = "아이디를 입력해주세요";
        checkResult.style.color = "black"; // 원하는 스타일로 변경하세요
        return;
    }
    $.ajax({
        // 요청방식:post, url:/user/checkuserid, data:userId
        type: "post",
        url: "/user/checkuserid",
        data: {"userId": userId},
        // 서버 응답 : 성공 / 실패
        success: function(response){
            console.log(response);
            if(response == "usable"){
                checkResult.innerHTML = "사용가능한 아이디입니다";
                checkResult.style.color = "blue";
            }else if (userId == "") {
                checkResult.innerHTML = "아이디를 입력해주세요";
            }

            else {
                checkResult.innerHTML = "이미 사용중인 아이디입니다";
                checkResult.style.color = "red";						
            }
        },
        error: function(error){
            console.log("에러발생", error);
        }
    });
}

 


* Model
@ModelAttribute : 폼 전체 전달할 때
                         => (@ModelAttribute) SampleDTO sampleDTO
@RequestParam() : 하나만 전달할 때
                         => @RequestParam("name") String name

json객체 변환기 필요 - pom.xml에 의존성 주입
jackson-databind

<!-- jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.0</version>
</dependency>



게시글 상세보기
- 조회수(updateHit) : sql - update boards set hit = hit+1 where id=#{id}
- 글상세보기 (findById)

 

* boardController

@GetMapping
public String getBoard(@RequestParam("id") Long id, Model model) {
    // 조회수 증가
    boardService.updateHit(id);
}

 

* boardMapper

<update id="updateHit">
    update boards set hit = hit + 1 
    where id = #{id};
</update>

 

 


게시글 수정
- boardupdate.jsp

* boardupdate

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>글 상세보기</title>
</head>
<body>
	<jsp:include page="../layout/header.jsp" />
	<div id="container">
		<section id="detail">
			<h2>글 상세보기</h2>
			<form action="/board/update" method="post">
				<input type="hidden" name="id" value="${board.id}">
				<table class="tbl_detail">
					<tr>
						<td><input type="text" name="boardTitle"
							value="${board.boardTitle}"></td>
					</tr>
					<tr>
						<td>작성자 : ${board.userId}</td>
					</tr>
					<tr>
						<td><textarea name="boardContent" >${board.boardContent}</textarea></td>
					</tr>
					<tr>
						<td>조회수 : ${board.hit}</td>
					</tr>
					<tr>
						<td>작성일 : <fmt:formatDate value="${board.createTime}"
								pattern="yyy-MM-dd HH:mm:ss" /></td>
					</tr>
					<tr>
						<td>
							<button type="submit">수정 완료</button>
							<button type="reset">수정 취소</button> 
							<a href="/board/"><button>목록</button></a>
						</td>
					</tr>
				</table>
			</form>
		</section>
	</div>
	<jsp:include page="../layout/footer.jsp" />
</body>
</html>

 

* boardController

// 게시글 수정
@GetMapping("/update")
public String update(Model model, @RequestParam("id") Long id) {
    BoardDTO baordDTO = boardService.findById(id);
    model.addAttribute("board",baordDTO);
    return "/board/boardupdate";
}

@PostMapping("/update")
public String update(@ModelAttribute BoardDTO boardDTO) {
    boardService.update(boardDTO);
    return "redirect:/board?id=" + boardDTO.getId();
}

 

 

* boardMapper

<update id="update">
    update boards 
    set boardtitle = #{boardTitle}, boardcontent = #{boardContent}
    where id = #{id}
</update>

 

 


게시글 삭제

- /delete

 

* boardController

// 게시글 삭제
@GetMapping("/delete")
public String delete(@RequestParam("id") Long id) {
    boardService.delete(id);
    return "redirect:/board/";
}

 

* boardMapper

<delete id="delete">
    delete from boards where id = #{id};
</delete>

 

 

 

 

React 공부

 

<<List_Delete>>
* App

  // app conponent에 onDelete 함수 생성
  const onDelete = (targetId) => {
    // targetId를 제외한 나머지 id를 가진 배열의 요소들만으로 다시 배열 생성
    const newDiaryList = data.filter((it) => it.id !== targetId);
    // 새로운 배열로 리스트를 전달해준다
    setData(newDiaryList);
  };
  
    return (
    <div className="App">
      <DiaryEditor onCreate={onCreate} />
      {/* DiaryList로 onDelete함수를 내려줌 */}
      <DiaryList diaryList={data} onDelete={onDelete} />
    </div>
  );




* DiaryList

// onDelete함수를 사용하진 않지만 받아서
const DiaryList = ({ onDelete, diaryList }) => {
  return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 있습니다</h4>
      <div>
        {diaryList.map((it) => (
          // DiaryItem에게 보내주어야 한다
          <DiaryItem key={it.id} {...it} onDelete={onDelete} />
        ))}
      </div>
    </div>
  );
};




* DiaryItem

// onDelete를 prop으로 받아준다
const DiaryItem = ({ author, content, create_date, emotion, id, onDelete }) => {
      <button
        onClick={() => {
          if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
            onDelete(id);
          }
        }}
      >
        삭제하기
      </button>
};







<<List_Update>>
리스트 데이터 수정하기
= 배열을 이용한 React의 List에 item을 동적으로 수정 해보기
=> with 조건부 렌더링

* App

const App = () => {
  // 수정할 targetId, newContent 두개의 매개변수를 받아온다
  const onEdit = (targetId, newContent) => {
    // setData 함수를 호추하고 map()함수를 적용해서
    // 일치하는 아이디를 찾아 해당 원소가 수정되도록 해준다
    setData(
      data.map((it) =>
        // 일치하는 경우 content를 newContent 로 수정, 일치하지 않으면 그냥 it
        it.id === targetId ? { ...it, content: newContent } : it
      )
    );
  };

  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate} />
      {/* DiaryList로 onDelete함수를 내려줌 */}
      {/* DiaryItem의 부모인 DiaryList로 onEdit함수를 보내준다 */}
      <DiaryList onEdit={onEdit} diaryList={data} onRemove={onRemove} />
    </div>
  );
};




* DiaryList

// onDelete함수를 사용하진 않지만 받아서
// onEdit prop까지 받아서 DiaryItem으로 전달
const DiaryList = ({ onEdit, onRemove, diaryList }) => {
  return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 있습니다</h4>
      <div>
        {diaryList.map((it) => (
          // DiaryItem에게 보내주어야 한다
          <DiaryItem key={it.id} {...it} onEdit={onEdit} onRemove={onRemove} />
        ))}
      </div>
    </div>
  );
};





* DiaryItem

// diaryItem component 생성

import { useRef, useState } from "react";

// onDelete를 prop으로 받아준다
// DiaryList에서 내려준 onEdit함수 prop을 받아서 호출해주면 된다
const DiaryItem = ({
  onRemove,
  onEdit,
  author,
  content,
  emotion,
  id,
  create_date,
}) => {
  // 수정하기 버튼을 눌렀을 때 나오는 수정 폼 => status로 만들기
  // isEdit = boolean 값으로 수정 중인지 아닌지를 판단
  const [isEdit, setIsEdit] = useState(false);
  // isEdit의 값을 반대로 바꾸는 반전연산 함수
  // true 값이라면 수정중으로 간주해서 수정 폼을 띄우도록 한다
  const toggleIsEdit = () => setIsEdit(!isEdit);

  //
  const localContentInput = useRef();

  // textarea의 input을 handling할 status 생성
  // useState(content) localContent의 기본값을 원래의 content값으로 설정해서
  // 수정하기 버튼을 누르면 원래의 내용을 불러올 수 있도록 해준다
  const [localContent, setLocalContent] = useState(content);

  // 밖으로 뺀 onClick의 함수를 새로 정의해줌
  const handleClickRemove = () => {
    if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
      onRemove(id);
    }
  };

  // 수정폼 내용 자체를 원래의 내용으로 초기화 해주는 함수
  const handleQuitEdit = () => {
    setIsEdit(false);
    setLocalContent(content);
  };

  // 수정완료 버튼을 눌렀을 때의 event를 처리할 함수
  const handleEdit = () => {
    // 5자 이상 입력하도록
    if (localContent.length < 5) {
      localContentInput.current.focus();
      return;
    }
    // 확인받기
    if (window.confirm(`${id}번 째 일기를 수정하시겠습니까?`)) {
      onEdit(id, localContent);
      // 수정이 완료되었기 때문에 토글을 한번더 실행해서 상태를 종료해준다
      toggleIsEdit();
    }
  };

  return (
    <div className="DiaryItem">
      <div className="info">
        <span className="author_info">
          | 작성자 : {author} | 감정점수 : {emotion} |
        </span>
        <br />
        <span className="date">{new Date(create_date).toLocaleString()}</span>
      </div>
      <div className="content">
        {/* 3항 연산자를 사용하여 isEdit의 상태에 따라 표시를 다르게 해줌 */}
        {isEdit ? (
          <textarea
            ref={localContentInput}
            // 값을 localContent에 mapping
            value={localContent}
            // onChange event handler에는 e event 객체를 받아서
            // 적힌 값을 localContent에 mapping 시켜서 사용할 수 있게 만들어준다
            onChange={(e) => setLocalContent(e.target.value)}
          />
        ) : (
          content
        )}
      </div>
      {/* 버튼의 onClick 함수가 길어서 밖으로 빼줌 */}
      {/* 수정하기가 활성중일 때 버튼도 따라서 달라짐 */}
      {isEdit ? (
        <>
          <button onClick={handleQuitEdit}>수정 취소</button>
          <button onClick={handleEdit}>수정 완료</button>
        </>
      ) : (
        <>
          <button onClick={handleClickRemove}>삭제하기</button>
          <button onClick={toggleIsEdit}>수정하기</button>
        </>
      )}
    </div>
  );
};

export default DiaryItem;



 

 

 

 

 

2023. 01. 04 (목)

관련글 더보기