Spring Framework-13

유호준·2021년 4월 3일
0

Spring Framework

목록 보기
14/21

💾이번엔 파일을 업로드해보자!

파일을 업로드나 다운로드하는 기능은 보안에 취약하기 때문에 조심해야합니다.


DB 수정

아래 SQL을 수행해서 file을 저장할 테이블을 생성해줍니다.

create table file(
	id int not null primary key auto_increment,
	name varchar(256),
	uuid varchar(40),
	upload_path vharchar(1000),
	post_id int,
	foreign key(post_id)
	references post(id));  

post.jsp 수정

글을 작성할 때 form을 아래와 같이 수정해서 파일을 전송할 수 있도록 만듭니다. 그리고 스크립트를 수정합니다. 글을 DB에 저장하고 나서 글의 id를 반환받습니다. 그리고 만약 form파일이 입력되어 있다면 file을 업로드합니다. 없다면 /board로 이동합니다.

<form action="/post" method="post" class="user">
	<div class="form-group">
		<input type="text" class="form-control form-control-user" name="title" placeholder="Title">
	</div>
	<div class="form-group">
		<textarea class="form-control form-control-user" name="content"></textarea>
        </div>
	<div class="form-group">
		<input type="file" id="files" multiple="multiple">
	</div>
	<a id="register" class="btn btn-primary btn-user btn-block">
		Register Post
	</a>
</form>
$("#register").click(function (){
        var postVO = new Object()
        var formData = new FormData()
        var files = $('#files')[0].files
        var postId

        postVO.title = $("[name='title']").val()
        postVO.content = $("[name='content']").val()

        $.ajax({
            method:"POST",
            url:"/post",
            data: JSON.stringify(postVO),
            contentType: "application/json; charset=UTF-8",
            success: function (data){
                postId = data

                if(files.length != 0){
                    for(var i=0; i<files.length; i++)
                        formData.append('files',files[i])

                    $.ajax({
                        method:"POST",
                        enctype: 'multipart/form-data',
                        url:"/"+postId+"/file",
                        data: formData,
                        processData: false,
                        contentType: false,
                        success:function(){
                            location.href='/board'
                        },
                        error: function (e){console.log(e)}
                    })
                }
                else
                    location.href='/board'
            }
        })
    })

설정 수정

WebConfig 클래스 수정

파일 업로드를 하기 위해서는 설정을 해주어야합니다. 아래와 같이 WebConfig 클래스를 수정합니다. UPLOAD_PATH는 파일이 업로드 될 경로입니다. 후에 FileService에서 사용하기 위해 publicstatic변수로 만들고, 수정할 수 없도록 final로 만들어줍니다.

중복되는 코드들은 생략합니다.

public class WebConfig implements WebApplicationInitializer {
    public final static String UPLOAD_PATH = "/Users/youhojoon/Desktop/CS/upload";
    private final long MAX_FILE_SIZE = 20971520; //파일 최대 크기 1024 * 1024 * 20 = 20MB
    private final long MAX_REQUEST_SIZE = 41943040; // 요청에서 받을 수 있는 최대 크기 40MB
    private final int FILE_SIZE_THRESHOLD = 20971520; // 파일이 디스크에 기록되는 크기 제한 20MB

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    	/*
        	생략
        */
 
        AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
        applicationContext.register(ServletConfig.class);
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher",
                new DispatcherServlet(applicationContext));
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping("/");

        MultipartConfigElement multipartConfigElement = new MultipartConfigElement(UPLOAD_PATH, MAX_FILE_SIZE, MAX_REQUEST_SIZE, FILE_SIZE_THRESHOLD);
        dispatcher.setMultipartConfig(multipartConfigElement);


        /* 
        	생략
         */
    }
}

ServletConfig 클래스 수정

이제 우리는 파일 요청을 서블렛을 통해 설정한 것처럼 받게 됩니다. 이 파일을 직접적으로 다루는 클래스를 ServletConfig에 설정해주어야 합니다. 그리고 파일 업로드도 로그인을 한 유저만 할 수 있도록 interceptor 경로에 추가해줍니다.

@Override
public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/board","/post/**","/*/file");
        registry.addInterceptor(new PostInterceptor()).addPathPatterns("/post/**");
}
    
@Bean
public StandardServletMultipartResolver standardServletMultipartResolver(){
	return new StandardServletMultipartResolver();
}

FileVO 생성

파일의 정보를 담을 FileVO 클래스를 생성합니다.

@Data
@Builder
public class FileVO {
    public int id;
    public String name; //파일의 이름
    public String uuid; //이름의 중복을 피하기 위해 앞에 붙는 랜덤한 문자열 ex)aad6af27-d71d-4a17-a627-b258cfe8b2fe
    public String uploadPath; //파일이 저장된 위치
    public int postId
}

@Builder Annotation은 lombokAnnotation으로 디자인 패턴의 Builder패턴을 만들어줍니다.


mybatis-config.xml 수정

FileVOalias를 추가해줍니다.

<configuration>

    <typeAliases>
        <typeAlias type="ac.kr.smu.vo.PostVO" alias="PostVO"/>
        <typeAlias type="ac.kr.smu.vo.UserVO" alias="UserVO"/>
        <typeAlias type="ac.kr.smu.vo.FileVO" alias="FileVO"/>
    </typeAliases>

</configuration>

FileMapper 인터페이스와 xml생성

파일의 내용을 저장할 FileMapper 인터페이스와 xml을 생성합니다.

public interface FileMapper {
    public void save(FileVO fileVO);
}
<mapper namespace="ac.kr.smu.mapper.FileMapper">
    <insert id="save">
        INSERT INTO file(name,uuid,upload_path,post_id)
        VALUES(#{name},#{uuid},#{uploadPath},#{postId})
    </insert>
</mapper>

FileService 생성

파일의 처리를 할 FileService를 생성합니다.

public interface FileService {
    public void saveAll(int postId,List<MultipartFile> files);
}
@Service
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {

    private final FileMapper fileMapper;

    @Override
    public void saveAll(int postId,List<MultipartFile> files) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date date = new Date();
        String upload_date = sdf.format(date);
        upload_date.replace("-", File.separator);
        File uploadPath = new File(WebConfig.UPLOAD_PATH,upload_date);

        if(!uploadPath.exists()) //현재 날짜의 폴더가 없다면 생성 ex) 2021/04/03
            uploadPath.mkdir();

        for(MultipartFile file : files){//파일을 하나씩 저장
            String fileName = file.getOriginalFilename();
            UUID uuid = UUID.randomUUID();
            FileVO fileVO = FileVO.builder().postId(postId).name(fileName).uuid(uuid.toString())
                    .uploadPath(uploadPath.toPath().toString()).build();

            fileName = uuid.toString() + "_" + fileName;
            File uploadFile = new File(uploadPath,fileName);
            try{
                file.transferTo(uploadFile);
                fileMapper.save(fileVO);
            }catch (IOException e){e.printStackTrace();}
        }
    }
}

FileController 생성

FileControllerview를 반환할 필요가 없으므로 @RestController로 작성합니다.

@RestController
@RequestMapping(value = "/{postId}/file")
@RequiredArgsConstructor
public class FileController {
    private final FileService fileService;

    @PostMapping
    public void postFile(@RequestParam("files") List<MultipartFile> files, @PathVariable("postId") int postId){
        fileService.saveAll(postId,files);
    }
}

PostMapper.xml 수정

INSERT를 하고나서 postVOid를 저장해줄 수 있도록 수정합니다. useGeneratedKeys속성은 DB에서 자동으로 설정한 키를 사용한다는 속성이고 기본값은 false입니다. keyProperty는 키가 저장될 필드입니다.

 <insert id="save" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO post(title, content, created_date, user_id)
        VALUES (#{title}, #{content}, #{created_date}, #{user.id});
</insert>

PostController 수정

글을 저장 후, 글의 id를 반환할 수 있도록 수정합니다.

@PostMapping
public @ResponseBody int postPost(@RequestBody PostVO postVO, UserVO user) {
        postVO.setUser(user);
        postService.save(postVO);
        return postVO.getId();
}

테스트





로그인을 하지않고 postman으로 요청을 보내면 아래처럼 로그인 페이지로 이동됩니다.

0개의 댓글