2023. 10. 30. 20:07ㆍSpringboot
1. Requirements
- Spring boot version 2.4.5
2. Stacks
Springboot
- Loombook
- Spring Data JPA
- Spring Boot DevTools
- Spring Security
- Spring Web
- Spring boot version 2.4.5
Mybatis
3. Code
1. host.jsp
- localhost:8080/host/host으로 접속했을 때 나오는 첫 페이지(이는 임시이며 바뀔 예정)
- 해당 밑에 있는 코드는 인증된 정보에 접근하는 방법이며 필자는 모든 jsp파일의 header.jsp에
적어줌으로써 다른 jsp파일은 안 적혀있습니다.
- ${principal}에 해당하는 변수를 사용할 시 꼭 필요합니다.
- 해당 jsp는 form형태를 post방식으로 보냅니다.
- 해당 페이지는 Springboot package com.pjh.company.config부분에 void configure(HttpSecurity http)에
http.authorizeRequest().antMatchers("/host.**")를 통해 로그인이 되지 않으면 접근 불가능하게 설계되어있습니다.
- 해당 페이지는 버튼을 누르면 모달이 켜지는 방식으로 설계되어있습니다.
- 이는 추후에 업로드를 버튼을 누르면 db 저장되어있는 정보를 카드형식으로 출력되어질 예정입니다.
- <style></style> 관련하여 추후 변경 예정입니다.
<!DOCTYPE html>
<html lang="en">
<head>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>pppp</title>
<link rel="stylesheet" href="/css/style.css">
<link rel="shortcut icon" sizes="76x76" type="image/x-icon"
href="https://a0.muscache.com/airbnb/static/logotype_favicon-21cc8e6c6a2cca43f061d2dcabdf6e58.ico">
</head>
<style>
.host-cards {
width: 900px;
margin: 20px auto 20px auto;
}
.card {
border-radius: 15px;
}
.card>button {
margin: 10px;
}
.card>img {
border-top-left-radius: 10px;
border-top-right-radius: 10px;
height: 200px;
padding: 5px;
}
.card-text {
margin: 10px auto 20px auto;
}
.myTeamMember {
background-color: white;
width: 500px;
margin: 20px auto 0px auto;
border: 1px gray;
padding: 20px;
box-shadow: 2px 2px 2px 2px gray;
border-radius: 5px;
}
</style>
<body>
<button id="openModal">파일 업로드 모달 열기</button>
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close-button" id="closeModal">X</span>
<form class="upload-form" action="/image" method="post" enctype="multipart/form-data">
<div class="upload-form-detail">
<input type="text" placeholder="제목" name="title"/>
<input type="textarea" placeholder="설명" name="description"/>
<input type="file" name="file" onchange="imageChoose(this)"/>
<button class="cta blue">업로드</button>
</div>
</form>
</div>
</div>
</body>
</html>
<script src="/js/host.js" ></script>
2. host.js
- javascript에서 받은 form형태를 fetch의 비동기 통신을 통하여 post방법으로 controller로 보냅니다.
- 해당 $. fetch({}) 안에 존재하는 url부분은 홑따옴표('')가 아닌 빽틱(``)입니다.
document.addEventListener("DOMContentLoaded", function () {
var button = document.querySelector("#openModal");
var modal = document.querySelector("#myModal");
var closeModal = document.querySelector("#closeModal");
var signupLink = document.querySelector("#signupLink");
button.addEventListener("click", function () {
modal.style.display = "block";
});
closeModal.addEventListener("click", function () {
modal.style.display = "none";
});
signupLink.addEventListener("click", function () {
modal.style.display = "none";
});
});
function imageChoose(input) {
const file = input.files[0];
if (file) {
// You can perform actions with the selected file here
console.log("Selected file: " + file.name);
}
}
// Function to handle file upload
document.getElementById("uploadButton").addEventListener("click", function() {
// Get the form and its data
const form = document.getElementById("uploadForm");
const formData = new FormData(form);
// Send the form data to the Spring Controller
fetch('/image', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
// Handle the response from the server
console.log(data);
})
.catch(error => {
// Handle any errors
console.error(error);
});
});
3. application.yml
- springboot를 실행할 때 기본적으로 필요한 요소를 적음
- datasource는 mariadb를 직접 지정해야 사용할 수 있음
- 변경점 없음
server:
port: 8080(자신이 7070으로 설정했다면 localhost:7070으로 들어가야함)
servlet:
context-path: /
encoding:
charset: utf-8
enabled: true
spring:
mvc:
view:
prefix: (src/main밑에 있는 폴더명에 저장)
suffix: .jsp
datasource:
driver-class-name: org.mariadb.jdbc.Driver
url: jdbc:mariadb://localhost:(mybatis 연결포트)/(자신이 연결한 데이터베이스 이름)?serverTimezone=Asia/Seoul
username: (자신이 만든 아이디)
password: (자신이 만든 비밀번호)
jpa:
open-in-view: true
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
show-sql: true
servlet:
multipart:
enabled: true
max-file-size: 2MB
security:
user:
name: test
password: 1234
4. ImageController.java
- @RequiredArgsConstrutor : private final키워드의 생성자를 사용할 수 있게 합니다.(의존성 주입)
- 이미지를 전송하는 것이라 @RestController로 착각할 수 있으나, 이는 파일을 리턴할 것이기에 @Controller 사용을 합니다.
package com.pjh.web;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import com.pjh.config.auth.PrincipalDetails;
import com.pjh.handler.ex.CustomValidationException;
import com.pjh.service.ImageService;
import com.pjh.web.dto.image.ImageUploadDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Controller
public class ImageController {
private final ImageService imageService;
@PostMapping("/image")
public String imageUpload(ImageUploadDto imageUploadDto,
@AuthenticationPrincipal PrincipalDetails principalDetails) {
// 서비스 호출
if(imageUploadDto.getFile().isEmpty()) {
throw new CustomValidationException("이미지가 첨부되지 않았습니다.", null);
}
imageService.사진업로드(imageUploadDto, principalDetails);
return "redirect:/host/host";
// 파일을 리턴하기 때문에 apiContrller메 사용하지 않음.
}
}
5. CustomValidationException.java
- ImageController.java에서 !imageUploadDto.getFile().isEmpty()일때
오류상황을 표기하기 위해 exception처리를 합니다.
package com.pjh.handler.ex;
import java.util.Map;
public class CustomValidationException extends RuntimeException{
// 객체 구분 할때
private static final long serialVersionUID = 1L;
private Map<String, String> errorMap;
public CustomValidationException(String message, Map<String,String> errorMap){
super(message);
this.errorMap = errorMap;
}
public Map<String, String> getErrorMap(){
return this.errorMap;
}
}
6. ControllerExceptionHandler.java
- 모든 Exception을 처리해주는 핸들러입니다.
- CustomValidationException을 새로 정의했기에 새롭게 만들어줍니다.
package com.pjh.handler;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import com.pjh.handler.ex.CustomApiException;
import com.pjh.handler.ex.CustomValidationApiException;
import com.pjh.handler.ex.CustomValidationException;
import com.pjh.util.Script;
import com.pjh.web.dto.CMRespDto;
@RestController // 데이터 리턴
@ControllerAdvice // 모든 exception을 낚아챔
public class ControllerExceptionHandler {
@ExceptionHandler(CustomValidationException.class)
// RuntimeException이 발생하면 모든 페이지가 낚아챔
public String validationException(CustomValidationException e) {
return Script.back(e.getErrorMap().toString());
//return new CMRespDto(-1, e.getMessage(), e.getErrorMap());
// 제네릭 타입 리턴은 <?>가 편하다
// 응답에 실패했고 에러메세지와 각각의 fieldError와 defaultMessage 리턴
}
@ExceptionHandler(CustomValidationApiException.class)
public ResponseEntity<?> validationApiException(CustomValidationApiException e){
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(), e.getErrorMap()),HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(CustomApiException.class)
public ResponseEntity<?> apiException(CustomApiException e){
return new ResponseEntity<>(new CMRespDto<>(-1, e.getMessage(),null), HttpStatus.BAD_REQUEST);
}
}
7. CMRespDto.java
- 해당 파일은 공통적으로 발생하는 응답에 대한 dto입니다.
- 변경점 없음
package com.pjh.comapny.web.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto<T> {
private int code; // 1(성공), -1(실패)
private String message;
private T data;
}
8. ImageUploadDto.java
- host.jsp에서 form형식에 담긴 title, description, multipartfile을 처리하기 위해 만들어진 dto입니다.
package com.pjh.web.dto.image;
import org.springframework.web.multipart.MultipartFile;
import com.pjh.domain.image.Image;
import com.pjh.domain.user.User;
import lombok.Data;
@Data
public class ImageUploadDto {
private MultipartFile file;
private String title;
private String description;
private String postImageUrl;
public Image toEntity(String postImageUrl, User user) {
return Image.builder()
.title(title)
.description(description)
.postImageUrl(postImageUrl) // uuid_getFile().getOriginalFileName()
.user(user)
.build();
}
}
9. ImageService.java
- @Service 비즈니스 로직을 구현을 위해 생성하는 service 어노테이션입니다.
- @Transactional DB에 트랜잭션(일의 최소 단위) 처리를 springboot에서 구현한 형태로 수행할 수 있게 합니다.
- UUID를 지정하여 같은 이미지 파일의 업로드 중복을 엄청나게 높은 확률로 제거합니다.
ex) 1.jpg가 업로드가 중복될 수 있기에 UUID.getRandom()를 활용하여
e5ab7114-de57-4ae5-9963-93351602bb72_1.jpg로 저장하게 만듦.
package com.pjh.service;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.pjh.config.auth.PrincipalDetails;
import com.pjh.domain.image.Image;
import com.pjh.domain.image.ImageRepository;
import com.pjh.web.dto.image.ImageUploadDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor // DI
@Service
public class ImageService {
private final ImageRepository imageRepository;
@Value("${file.path}")
private String uploadFolder;
// application.yml에 해당하는 file : path :로 들어옴
public void 사진업로드(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails) {
UUID uuid = UUID.randomUUID();
String imageFileName = uuid+"_"+imageUploadDto.getFile().getOriginalFilename();
// 업로드하려는 파일 네임이 String으로 들어옴
System.out.println("이미지 파일이름 : "+imageFileName);
Path imageFilePath = Paths.get(uploadFolder+imageFileName);
// 실제 저장 장소
try {
Files.write(imageFilePath, imageUploadDto.getFile().getBytes());
}catch(Exception e) {
e.printStackTrace();
}
// 통신이 일어나거나 I/O이 일어날때 -> 예외가 발생할 수 있음.
Image image = imageUploadDto.toEntity(imageFileName, principalDetails.getUser());
// toEntity() -> db에 해당 toEntity화한 imageFileName, princiaplDetails.getUser()를 저장하겠다.
// db에 업로드를 진행한 유저아이디 + 업로드한 파일을 저장
imageRepository.save(image);
// System.out.println(imageEntity.toString());
// 무한 참조 조심, User객체에 Image -> Image객체에 User
}
}
10. ImageRepository
- DB와 직접 맞닿아 있는 인터페이스입니다.
- DB에 처리할 SQL문을 직접 사용할 수 있고 extends 하여 JpaRepository의 crud형태의 기본적인 연산이 지원됩니다.
package com.pjh.domain.image;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ImageRepository extends JpaRepository<Image, Integer>{
}
'Springboot' 카테고리의 다른 글
스프링 이미지 업로드(2) (0) | 2023.11.07 |
---|---|
스프링 메모장 (1) | 2023.11.01 |
스프링 회원 수정 (1) | 2023.10.23 |
스프링 회원가입(2) (1) | 2023.10.23 |
스프링 회원가입(1) (2) | 2023.10.20 |