자바풀스택 과정/자바 풀 스택 : 수업내용정리

자바 풀 스택 2/27 오전 기록 064-1

파티피플지선 2025. 2. 27. 14:10

9:27 학원 도착

오전 시간에는 1시간 리액트 나간다고 해서 준비해둠.

 

 

<9:30 1교시>

비쥬얼 스튜디오 코드에 확장 프로그램 4개 설치하기

 

 

 

나는 와서 헬로우 폴더에서 터미널 열었었는데 이번에는 react_work에서 만들어야 해서 경로 바꿔서 들어감

 

 

create-react-app basic을 입력하면 basic이라는 앱을 개발할 환경(web pack)이 구성된다.

구성되는데는 시간이 걸리고, 구성된 후 cd 앱이름 npm start 로 앱 개발 시작

 

 

창 뜬거 두개 닫고 우리는 vs code에서 터미널을 열어서 할거임

 

터미널 닫으면 서버도 죽는줄 알았는데 서버는 돌아가고 있음.

 

Y누르면 3001번 포트에서 돌아감

 

 

package.json은 maven에서 pom.xml과 유사한 역할을 함.

@d어쩌구로 필요한 library들(외부 라이브러리들)은 node_modules에 설치가 되어 있다.

node_modules는 복사해서 다니지 않기로 한다.

node_modules가 없을 때 npm install을 하면 node_modules 폴더가 생성된다.

처음에 create-react-app을 입력하면 자동으로 install 되어 있다.

 

src와 public 이 있는데 public은 스프링부트에서의 static 폴더라고 생각하면 돼서 이 폴더에 있는 것은 클라이언트가 요청하면 받아 갈 수 있는 내용들이 들어 있다는 것이고, 주소창에서는 localhost:3001을 입력해서 보이는 창은 이 폴더에 있는  내용들을 (index.html의 일부가 해석돼서 전달되긴 함) 여기에 뿌려준다는 것이다.

 

nodejs 입장에서 인덱스는 자바에서 jsp 같은 페이지라고 이해하면 된다.

 

서버에서는 빈 문자열을 줬는데 웹브라우저가 알아서 src 폴더에 코딩된 내용을 컴파일해서 뿌려주는 형태의 CSR(client side rendering)이다. 

 

일부는 서버에서 해석해가지고 전달하기도 한다.

 

index.js를 보면 아래와 같은 코드를 확인할 수 있는데,

id가 root 인 div 안을 App.js 에서 리턴해준 component로 채운다는 의미를 내포하고 있다.

즉, 이것이 리액트의 도입 시작점이라고 생각하면 된다.

(마치 스프링부트에서 MainApplication의 메인 메소드 역할하는 클래스처럼)

 

 

 

 

.js라는 의미는 이 부분이 전부 자바스크립트 영역이라는 의미이다. 

그래서 이 영역에서는 <div class=""> 형태로 작성하는게 어렵다.

그래서 className이라는 예약어를 사용해서 class를 나타낼 수 있게 해준다.

 

 

 

 

 

이미지를 import 해서 log라는 변수에 담을 수 있다.

 

 

 

 

이미지의 크기를 조절하고 싶으면 일단 inline css를 작성하는 방법이 있지.

<img src={logo} alt="" style="width:100px; height:200px;">

하지만 리액트에선 이걸 허용하지 않는다.

 

리액트에서는 스타일을 적용할 때에도 inline css를 작성하기 위한 오브젝트를 사용해야 한다.

 

 

 

 

 

App.css내용 다 지우고 어떻게 작동하는지 볼거임.

 

 

글로벌로 적용하고 싶은 Css는 App.css에 작성해서 import 하면 된다.

 

 

 

 

<10:30 2교시>

만들었던 기능 중에서 내가 쓴 글 내가 삭제하기 눌러보면 삭제가 된다.

일반적인 유저면 이렇게 쓰겠지만, 이상한 애들이 있다.

로그인만 되어 있으면 주소창 입력을 통해서 다른 사람의 글을 삭제하려는 이상한 애들.

 

서비스에서 인증 작업을 추가해주려고 한다.

글 작성자와 로그인된 사용자가 다르면 경로를 요청하지 못하게 하는 코드를 작성해주려고 한다.

PostServiceImpl로 가서 작성하고.

 

에러 발생했을 때 처리해줄 예외 클래스도 만든다.

이런 식으로 에러 페이지를 응답하게 된다.

 

 

정리

 

 

이 기능을 이제 수정에도 써야하고 다른 곳에서도 틈틈이 써야할텐데, 라는 이야기가 나오면서 메인 비즈니스 로직과 동떨어진 예외 처리 기능들인데라고 말씀하신 순간 AOP로 만들어서 원하는 때 사용할 수 있게 만들면 편리하겠구나, 라는 생각이 들었는데 그렇게 하면 된다고 말씀을 하셨다.

 

 

 

AuthAspect라고 되어 있는데, 뭔가 다른거였어도 좋을거 같긴하다. 정말 프로그래밍 중에는 이름짓기가 까다로운 부분인가보다.

 

 

 

 

 

4교시에는 AOP했던 Spring08 다시 봐봐야겠다. 확실히 짚고 넘어가야징.

 

<11:30 3교시>

 

 

 

아래는 선생님 코드인데 나는 더 간단하게 만들어 보고 싶음

더보기

 

package com.example.spring10.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import com.example.spring10.dto.CommentDto;
import com.example.spring10.dto.PostDto;
import com.example.spring10.exception.DeniedException;
import com.example.spring10.repository.CommentDao;
import com.example.spring10.repository.PostDao;

@Component // bean 으로 만들기 위한 어노테이션
@Aspect // aspect 역활을 하기 위한 어노테이션 
public class AuthAspect {
	
	@Autowired private PostDao postDao;
	@Autowired private CommentDao commentDao;
	
	@Around("execution(* com.example.spring10.service.*.delete*(..)) || execution(* com.example.spring10.service.*.update*(..)) ")
	public Object checkWriter(ProceedingJoinPoint joinPoint) throws Throwable {
		//aop 가 적용된 메소드명 얻어내기 
		String methodName=joinPoint.getSignature().getName();
		System.out.println(methodName+" 메소드에 aop 가 적용됨");
		
		//수정, 삭제 작업을 할 글번호
		long num=0;
		
		//매개변수에 전달된 데이터를 Object[] 로 얻어내기
		Object[] args = joinPoint.getArgs();
		//반복문 돌면서 원하는 type 을 찾는다.
		for(Object tmp:args) {
			if(tmp instanceof Long) {
				num=(long)tmp;
			}else if(tmp instanceof PostDto) {
				num=((PostDto) tmp).getNum();
			}else if(tmp instanceof CommentDto){
				num=((CommentDto) tmp).getNum();
			}
		}
		
		String writer=null;
		
		if(methodName.contains("Post")) {
			writer=postDao.getData(num).getWriter();
		}else if(methodName.contains("Comment")) {
			writer=commentDao.getData(num).getWriter();
		}
		//로그인된 userName
		String userName=SecurityContextHolder.getContext().getAuthentication().getName();
		
		if(!writer.equals(userName)) {
			throw new DeniedException("요청이 거부 되었습니다.");
		}
		
		//aop 가 적용된 메소드를 실행한다 
		Object obj=joinPoint.proceed();
		
		return obj;
	}
}

 

아래는 내가 삽질해보는 과정

더보기
package com.example.spring10.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import com.example.spring10.dto.CommentDto;
import com.example.spring10.dto.PostDto;
import com.example.spring10.exception.DeniedException;
import com.example.spring10.repository.CommentDao;
import com.example.spring10.repository.PostDao;

@Component //bean으로 만들기 위한 어노테이션
@Aspect //aspect 역할을 하기 위한 어노테이션
public class AuthAspect {
	
	@Autowired private PostDao postDao;
	@Autowired private CommentDao commentDao;
	//메소드의 리턴 타입은 모두 가능하고, 아래 패키지 속에 속해 있는 모든 클래스 중에서 delete로 이름이 시작하는 모든 메소드의 모든 .. 매개변수
	@Around("execution(* com.example.spring10.service.*.delete*(..))|| execution(* com.example.spring10.service.*.update*(..))") //어떤 값을 들고 있는지 모르니까
	public Object checkWriter(ProceedingJoinPoint joinPoint) throws Throwable {
		//aop가 적용된 메소드명 얻어내기
		String methodName=joinPoint.getSignature().getName();
		System.out.println(methodName+"메소드에 aop가 적용됨");
		
		//수정, 삭제 작업을 할 글번호
		long pnum=0;
		//수정, 삭제 작업을 할 댓글 번호
		long cnum=0;
		
		String writer=null;
		if(methodName.contains("Post")) {
			writer=postDao.getData(pnum).getWriter();

		}else if(methodName.contains("Comment")) {
			writer=commentDao.getData(cnum).getWriter();
		}
		String userName=SecurityContextHolder.getContext().getAuthentication().getName();
		
		if(!writer.equals(userName)) {
			throw new DeniedException("요청이 거부되었습니다.");
		}
			
		
		//매개변수에 전달된 데이터를 Object[]로 얻어내기
		Object[] args= joinPoint.getArgs();
		//반복문 돌면서 원하는 type을 찾는다
		for(Object tmp:args) {
			if(tmp instanceof Long) {
				pnum=(long)tmp;
				
				
				/*
				 * //글번호 long num=(long)tmp; //글 작성자와 로그인된 userName과 다르면 Exception을 발생시키고
				 * ExceptionController에서 처리하게 한다. String writer =
				 * postDao.getData(num).getWriter(); String userName =
				 * SecurityContextHolder.getContext().getAuthentication().getName();
				 * if(!writer.equals(userName)) { throw new
				 * DeniedException("요청이 거부되었습니다! 다른 사람의 글을 삭제할 수 없습니다!"); }
				 */
			}else if(tmp instanceof PostDto) {
				pnum=((PostDto)tmp).getNum();
			}else if(tmp instanceof CommentDto) {
				pnum=((CommentDto)tmp).getNum();
			}
		}
			
		//aop가 적용된 메소드를 실행한다
		Object obj=joinPoint.proceed();
		
		return obj;
	}
}

 

 

같이 듣는 애의 질문이 있어서 그걸 해결하기 위해 확장 프로그램 설치

 

이걸 설치하면 원하는 URL에 원하는 메소드를 테스트할 수 있음

 

 

 

 

 

 

 

에러가 나서 아직 이해를 못했는데 아마 내가 해결하고 싶은 AuthAspect 부분 해결하고나면 해결될지도 모르겠다.

 

앞으로도 tabbed postman을 사용할 상황이 많을 것이라고 한다.

 

 

아무튼 AOP 배운뒤 처음으로 사용해본 것이라고 함.

 

그리고 오후 시간에는 자료실을 만들게 될 것 같음.

 

 

 

자료실의 기능들을 생각해보자. 게시글 목록과 비슷하지만 자세히 보기는 없고, 파일 명을 누르면 파일이 다운로드 되면 되는데 그게 원본 파일의 이름과 동일하게 다운로드되어야 한다는 것. 파일의 크기가 있어야 다운로드가 가능한것, 누가 어떤 타이틀로 어떤 파일을 언제 업로드했는지 등등. Post와 비슷하지만 File에선 다른 부분을 다룰 것.

File이 업로드 되고 다운로드 되는 예제는 spring07에서 해봤다.

업로드할려면 로그인해야 하고, 수정삭제도 본인만 가능.

 

 

<12:30 4교시>

 

아까전에 해결하고 싶었던 AuthAspect 부분 해결함. 그래서 Tabbed Postman 기능도 확인함.

내가 작성한 aop 부분 기능 성공적(샘도 이렇게 할지 아까 보내준것처럼 할지 고민하셨다고 함)

더보기
package com.example.spring10.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import com.example.spring10.dto.CommentDto;
import com.example.spring10.dto.PostDto;
import com.example.spring10.exception.DeniedException;
import com.example.spring10.repository.CommentDao;
import com.example.spring10.repository.PostDao;

@Component //bean으로 만들기 위한 어노테이션
@Aspect //aspect 역할을 하기 위한 어노테이션
public class AuthAspect {
	
	@Autowired private PostDao postDao;
	@Autowired private CommentDao commentDao;
	//메소드의 리턴 타입은 모두 가능하고, 아래 패키지 속에 속해 있는 모든 클래스 중에서 delete로 이름이 시작하는 모든 메소드의 모든 .. 매개변수
	@Around("execution(* com.example.spring10.service.*.delete*(..))|| execution(* com.example.spring10.service.*.update*(..))") //어떤 값을 들고 있는지 모르니까
	public Object checkWriter(ProceedingJoinPoint joinPoint) throws Throwable {
		//aop가 적용된 메소드명 얻어내기
		String methodName=joinPoint.getSignature().getName();
		System.out.println(methodName+"메소드에 aop가 적용됨");
		
		//수정, 삭제 작업을 할 글번호
		long pnum=0;
		//수정, 삭제 작업을 할 댓글 번호
		long cnum=0;
		//작성자
		String writer=null;
		//현재 유저
		

		//매개변수에 전달된 데이터를 Object[]로 얻어내기
		Object[] args= joinPoint.getArgs();
		//반복문 돌면서 원하는 type을 찾는다
		for(Object tmp:args) {
			if(tmp instanceof Long) {
				if(methodName.contains("Post")) {
					pnum=(long)tmp;
					writer=postDao.getData(pnum).getWriter();
				}else if(methodName.contains("Comment")) {
					cnum=(long)tmp;
					writer=commentDao.getData(cnum).getWriter();
				}
			}else if(tmp instanceof PostDto) {
				pnum=((PostDto)tmp).getNum();
				writer=postDao.getData(pnum).getWriter();
			}else if(tmp instanceof CommentDto) {
				cnum=((CommentDto)tmp).getNum();
				writer=commentDao.getData(cnum).getWriter();
			}
		}
		String userName=SecurityContextHolder.getContext().getAuthentication().getName();
		if(!writer.equals(userName)) {
			throw new DeniedException("요청이 거부되었습니다.");
		}	
		
		
		//aop가 적용된 메소드를 실행한다
		Object obj=joinPoint.proceed();
		
		return obj;
	}
}

 

 

 

오후에는 자료실 직접 만들어보기.

1. Dto와 데이터베이스에 테이블&시퀀스 만들기는 완료

보기 편할려고 하나 더 가져온 테이블 이미지

2. controller 만들기

3.  dao와 daoimpl, mapper 만들기

4. service와 serviceimpl 만들기

 

일단 클래스랑 인터페이스부터 만들자

 

그러다 발견한 깜빡한거, 테이블에 num으로 하기로 했었지.

 

 

자 일단 아무것도 안보고 배웠던것 중에서 기억나는대로 작성해본 list.html의 코드임. 지금 걱정되는 부분은 타임리프로 반복문 돌면서 목록 찍어내는거랑, 파일 이름을 쓸 때 org 써야할지 save 써야할지 헷갈린다는거. 그리고 다운로드 횟수 카운트 하는 부분이랑 페이징 처리가 안들어 간거. 등등이 있겠네. 

 

일단 실행해봤는데 너무 구려서 bootcss도 적용하려고 함.

 

아무래도 제일 먼저 해결해야 할 기능은 파일 업로드하기고, 그게 수업시간에 했던 내용이랑 겹치니까 쉬울 것 같음.

어쨋든 html 페이지 글 목록리스트의 html 페이지와 비교해보면서 수정먼저 하고.

 

일단 크게 건드릴건 없는데 tmp 쓰면 구려보이니까 바꾸긴 해야겠다. 그리고 list 받아오는 부분도 페이징 처리해야하니까 글 목록에서 했던것 처럼 FileListDto도 만들어야지.

그리고 매번 멍청이같이 빼먹는 th:href의 th: 도 잘 작성해줘야지.