| Today I Leanred
1. 회원 탈퇴
- 정책에 따라 탈퇴회원의 정보를 특정 기간 동안 가지고 있을 수 있다.
- 오늘 수업에서는 아래와 같이 회원을 초기화하되, 아이디 정보만 가지고 있는 식으로 만들었다.
- 회원 정책으로는, 보통 아래처럼 아이디를 쌩으로 다 보여주지 않고 마스킹 처리를 하는 게 일반적이고, 약관에 따라 데이터를 저장하는 기간도 기억상 아마도 1년 정도였던 것 같은데, 개인정보보호법에 따라 정해진 기간 이상은 탈퇴 회원 정보를 가지고 있을 수 없다. 비밀번호 갱신에 대한 안내도 정기적으로 이루어지는데, 오늘 배운 내용보다 실제 서비스 로직은 훨씬 복잡할 것 같다.
2. BindingResult
- 모델을 통한 데이터 바인딩 시 타입 미스매치와 같은 오류를 감지하는 역할
- 자세한 내용 : https://jaimemin.tistory.com/1874
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 |