simDev1234
심플하고 차분하게
simDev1234
전체 방문자
오늘
어제
  • 분류 전체보기
    • Computer Science
      • Basic Math
      • Data Structure
      • Algorithm
      • Database
      • OS
    • Language
      • Java
      • Kotlin
      • SQL
    • Framework
      • Spring
      • Orm&Mapper
      • 프로젝트로 스프링 이해하기
      • 스프링 라이브러리
    • Infra
      • Cloud
      • Docker
      • Redis
      • AWS, Azure
      • Device
    • Etc
      • CleanCoding
    • Git,Github

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 스프링
  • 컨트롤러
  • 참조변수
  • 404
  • scanner #next() #nextLine()
  • JVM메모리구조
  • controllerTest
  • 자바프로그램
  • null
  • 자바
  • 자바메모리구조
  • 자바프로그래밍
  • 참조타입

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
simDev1234
Framework/프로젝트로 스프링 이해하기

[LMS 만들기] 회원 탈퇴, 강좌 관리, 파일 업로드

[LMS 만들기] 회원 탈퇴, 강좌 관리, 파일 업로드
Framework/프로젝트로 스프링 이해하기

[LMS 만들기] 회원 탈퇴, 강좌 관리, 파일 업로드

2022. 10. 10. 07:13

| Today I Leanred

1. 회원 탈퇴

- 정책에 따라 탈퇴회원의 정보를 특정 기간 동안 가지고 있을 수 있다.

- 오늘 수업에서는 아래와 같이 회원을 초기화하되, 아이디 정보만 가지고 있는 식으로 만들었다.

- 회원 정책으로는, 보통 아래처럼 아이디를 쌩으로 다 보여주지 않고 마스킹 처리를 하는 게 일반적이고, 약관에 따라 데이터를 저장하는 기간도 기억상 아마도 1년 정도였던 것 같은데, 개인정보보호법에 따라 정해진 기간 이상은 탈퇴 회원 정보를 가지고 있을 수 없다. 비밀번호 갱신에 대한 안내도 정기적으로 이루어지는데, 오늘 배운 내용보다 실제 서비스 로직은 훨씬 복잡할 것 같다.

 

2. BindingResult

- 모델을 통한 데이터 바인딩 시 타입 미스매치와 같은 오류를 감지하는 역할

- 자세한 내용 : https://jaimemin.tistory.com/1874

 

[SpringBoot] Validation 간단 정리 - 1 (BindingResult, Validator)

개요 실무 프로젝트를 경험하신 분들은 다들 공감하시겠지만 프로젝트 개발 과정에서 실제 개발에 투자하는 시간만큼 예외처리 및 validation을 진행합니다. 해당 게시글은 PRG 패턴을 예시로 들어

jaimemin.tistory.com

 

3. 파일 업로드

- 파일 업로드를 할 때는 데이터를 한 번에 다 보내지 않고, 조각조각 나누어서 보내는 Multil-part 방식을 사용할 수 있다.

== 클라이언트가 요청을 보낼 때, HTTP 바디 부분에 데이터를 조각조각 나누어서 전송한다.

- 자세한 Multipart에 대한 내용 : https://codingnotes.tistory.com/73

 

- 파일 업로드 코딩 절차

구분 클라이언트 서버
작업 html request객체, controller, service
<form enctype = "multi-part/form-data">
<input type = "file" name = "file"/>
[1] 컨트롤러에서 새로운 로컬 저장 경로 및 URL 경로를 얻는다

[2] 로컬 저장 경로에 파일을 저장한다.

@PostMapping
public String addSubmit(MultipartFile file) {

     // 로컬 파일 경로 지정
     File newFile = new File(saveFileName); 
     // 파일 카피 후 저장
     FileCopyUtils.copy(file.getInputStream(),
                                    new FileOutputStream(newFile));

}

[3] Request Parameter 객체 안에 로컬 저장 경로와, url 경로를 담는다.

class Request 클래스 {
    String saveFilename;
    String urlFilename;
}

[4] 서비스에서 Request Parameter의 두 파일 경로를 DB에 저장한다.
(CREATE, UPDATE 시 파일 경로 저장)

(1) 클라이언트 

(1-1) form의 인크립트 타입을 "multi-part/form-data"로 지정해주어야 하며

<form id = "submitForm" method = "post" enctype="multipart/form-data">

(1-2) input의 타입을 file로 지정해주어야한다.

<input type = "file" name = "file"/>

(2) 서버

(2-1) 컨트롤러의 메소드 매개변수로 MultipartFile 를 받는다. (첨부파일)

@PostMapping(value = {"/admin/course/add.do", "/admin/course/edit.do"})
public String addSubmit(Model model, HttpServletRequest request,
                        CourseInput parameter, MultipartFile file) {

또는 리스트 형태로 받을 수 있다.

@PostMapping(value = {"/admin/course/add.do", "/admin/course/edit.do"})
public String addSubmit(Model model, HttpServletRequest request,
                        CourseInput parameter, List<MultipartFile> file) {

※ 컨트롤러에 롬복 @Slf4j을 통해 로그를 작성하도록 한다. (파일 저장 로그 확인)

@Controller
@RequiredArgsConstructor
@Slf4j
public class AdminCourseController extends BaseController{

(2-2) 인텔리J 에디터를 활용해 Resource Directions 루트 지정하기

[file]-[project structrure]-[modules]

(2-3) 컨트롤러에서 파일을 저장할 로컬 경로와, 웹상에서 사용할  URL 경로를 지정한 후 저장한다.

* 일반적으로 파일은 로컬에 저장한다.

* 로컬 파일 디렉토리는 연월일로 잡을 수 있으며, 파일명은 UUID로 암호화한다.

ㄴ 이는 중복된 파일명이 생기는 걸 방지함과 동시에, 저장 일시에 따라 파일을 관리하기 위함이다.

* 웹상에서 파일 곧 리소스를 가져올 땐 URL을 통해 가져온다. 

구분 예시
로컬 Path /spring/prj-01-LMS/fastlms/files/cat.png
url Path /files/cat.png
https://www.spring.com/files/cat.png

-  스프링 시큐러티의 WebSecurity를 통한 파일 URL 허용

@Override
public void configure(WebSecurity web) throws Exception {

    // 아래 허용
    web.ignoring().antMatchers("/favicon.png", "/files/**");

    super.configure(web);
}

 

-  신규 파일은 복사본으로 저장하며,  중복된 파일을 처리할 수 있도록, 연-월-일 디렉토리 안에 UUID로 암호화한 파일명으로 저장한다.

private String[] getNewSaveFile(String baseLocalPath, String baseUrlPath, String originalFilename) {

    // A. 로컬 파일 경로
    // 금일 연-월-일에 해당하는 디렉토리 경로 문자열 생성
    LocalDate now = LocalDate.now();

    String[] dirs = { // 연/월/일 경로
            String.format("%s/%d/", baseLocalPath, now.getYear()),
            String.format("%s/%d/%02d/",baseLocalPath, now.getYear(),now.getMonthValue()),
            String.format("%s/%d/%02d/%02d/",baseLocalPath, now.getYear(),
                    now.getMonthValue(), now.getDayOfMonth())
    };

    // 연-월-일 문자열 경로에 따라 디렉토리 생성
    for (String dir : dirs) {
        File file = new File(dir);
        if (!file.isDirectory()) { // 해당 경로가 존재하고 디렉토리인가? X
            file.mkdir(); // 해당 경로의 디렉토리를 생성한다.
        }
    }

    // B. URL 파일 경로
    String urlDir = String.format("%s/%d/%02d/%02d/",baseUrlPath, now.getYear(),
            now.getMonthValue(), now.getDayOfMonth());

    // C. 기존 파일 확장자
    String fileExtension = "";
    if (originalFilename != null){
        int dotPos = originalFilename.lastIndexOf(".");
        if (dotPos > -1) {
            fileExtension = originalFilename.substring(dotPos + 1);
        }
    }

    // D. 최종 파일 경로 추출
    // D-1.새로운 파일명(UUID)
    String uuid = UUID.randomUUID().toString().replace("-","");
    // D-2.신규 로컬 파일 경로
    String newLocalFileName = String.format("%s%s", dirs[2], uuid);
    // D-3.Url 파일 경로
    String newUrlFilename = String.format("%s%s", urlDir, uuid);
    // D-4.확장자 추가
    if (fileExtension.length() > 0) {
        newLocalFileName += "." + fileExtension;
        newUrlFilename += "." + fileExtension;
    }

    return new String[]{newLocalFileName, newUrlFilename};
}

/* 강좌 등록 */
@PostMapping(value = {"/admin/course/add.do", "/admin/course/edit.do"})
public String addSubmit(Model model, HttpServletRequest request,
                        CourseInput parameter, MultipartFile file) {

    String saveFileName = ""; // 로컬 저장 경로
    String urlFileName = "";  // URL 경로

    // 파일이 있으면
    if (file != null) {

        // 기존 파일 경로
        String originalFilename = file.getOriginalFilename();

        // 로컬 저장 경로 : 로컬 저장소에, 기존 파일명을 UUID로 암호화한 파일을 연/월/일 하위에 위치
        String baseLocalPath = "C:\\sebinSample\\spring\\prj-01-LMS\\fastlms\\files";
        // 불러올 경로 : URL 위치 - /files/하위
        String baseUrlPath = "/files";

        // 로컬 저장 경로 및 URL 경로 반환
        String[] arrFilename = getNewSaveFile(baseLocalPath, baseUrlPath, originalFilename);
        saveFileName = arrFilename[0];
        urlFileName = arrFilename[1];

        // 신규 파일 저장 - 1) 파일 객체로 신규 로컬 경로 감싼 후,
        //                2) file stream을 통해 기존 파일을 해당 경로로 복사하여 저장
        File newLocalFile = new File(saveFileName);

        try {
            FileCopyUtils.copy(file.getInputStream(),new FileOutputStream(newLocalFile));
        } catch(Exception e) {
            log.info(e.getMessage());
        }

    }

    // 파라미터에 파일 경로 저장
    parameter.setSaveFilename(saveFileName);
    parameter.setUrlFilename(urlFileName);

    boolean editMode = request.getRequestURI().contains("/edit.do");

    if (editMode) {

        long id = parameter.getId();

        CourseDto existCourse = courseService.getById(id);

        if (existCourse == null) {
            // error 처리
            model.addAttribute("message", "강좌정보가 존재하지 않습니다.");
            return "common/error";
        }

        boolean result = courseService.set(parameter);

    } else {
        boolean result = courseService.add(parameter);
    }

    return "redirect:/admin/course/list.do";
}

(3) DB에 신규 파일의 로컬 경로와 URL 경로를 저장한다.

- 위의 경우에는 강좌 상세 설정에 대한 등록 및 수정 뷰페이지에서 파일 업로드를 했다.

 

4. 톰캣을 통해 실행해보기

* 사전 작업 : 톰캣을 다운 받고 위치를 확인한다.

(4-1) 실행 버튼 옆의 [Add configuration] or [Edit configuration] 을 통해 [Run/Debug Configuration] 창을 연다.

(4-2) [+] 버튼을 통해, 톰캣 local 서버 설정을 추가한다.

(4-3) 톰캣 경로를 잡아준 뒤, [Fix]를 눌러 내보낼 압축파일을 선택한다. *여기서는 .war exploded를 선택했다.

(4-4) application context는 최상위 /로 선택한다.

(4-5) 다시 원래대로 돌아와 아래를 체크해주면, 저장 시 서버를 재가동하지 않고도 리로드 된다. (일부만)

(4-6) 타겟 폴더를 지우고 톰캣으로 실행한다.

'Framework > 프로젝트로 스프링 이해하기' 카테고리의 다른 글

[이커머스 프로젝트] Docker란 무엇인가? (자료 정리)  (0) 2022.11.24
[이커머스 프로젝트] 주제 선정, Microservice 아키텍처 이해, 기능 단위의 계획, 시스템 구성도 그리기(draw.io)  (0) 2022.11.23
[LMS 만들기] 회원정보 수정 - 우편번호 찾기, ajax와 Rest API  (0) 2022.10.10
[LMS 만들기] @Controller 와 @RestController 정확히 알기, AJAX로 Json 데이터 전송하기, Principle(로그인 정보)  (0) 2022.10.08
[LSM 만들기] 강좌 목록 구현하기 - 스마트에디터, 등록/수정 동시 처리  (0) 2022.10.07
  • | Today I Leanred
  • 1. 회원 탈퇴
  • 2. BindingResult
  • 3. 파일 업로드
  • - 파일 업로드 코딩 절차
  • 4. 톰캣을 통해 실행해보기
'Framework/프로젝트로 스프링 이해하기' 카테고리의 다른 글
  • [이커머스 프로젝트] Docker란 무엇인가? (자료 정리)
  • [이커머스 프로젝트] 주제 선정, Microservice 아키텍처 이해, 기능 단위의 계획, 시스템 구성도 그리기(draw.io)
  • [LMS 만들기] 회원정보 수정 - 우편번호 찾기, ajax와 Rest API
  • [LMS 만들기] @Controller 와 @RestController 정확히 알기, AJAX로 Json 데이터 전송하기, Principle(로그인 정보)
simDev1234
simDev1234
TIL용 블로그. * 저작권 이슈가 있는 부분이 있다면 댓글 부탁드립니다.

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.