Spring Framework-14

유호준·2021년 4월 3일
0

Spring Framework

목록 보기
15/21

📥 이번에는 글과 연결된 파일을 다운로드해보자!


post.jsp 수정

버튼을 추가해서 글에 연결된 파일이 있을 시 버튼을 만들어 다운로드 할 수 있도록 수정합니다.

<form id="modifyForm" action="/post" method="PUT" class="user">
	<input type="number" name="id" value="${post.id}" hidden>
	<div class="form-group">
		<input type="text" class="form-control form-control-user" name="title" placeholder="Title" value="${post.title}" readonly>
	</div>
	<div class="form-group">
		<textarea class="form-control form-control-user" name="content" readonly>${post.content}</textarea>
	</div>
	<div class="form-group">
		<input type="text" class="form-control form-control-user" name="name" placeholder="name" value="${post.user.name}" readonly>
	</div>
	<c:if test="${post.fileList.size() != 0}">
		<c:forEach var="file" items="${post.fileList}">
			<a href="/${post.id}/file/${file.id}" class="btn btn-primary btn-user btn-block">
            			${file.name}
			</a>
		</c:forEach>
	</c:if>
				아래 생략
                                   

PostVO 수정

글과 연결된 파일들을 저장할 fileList 필드를 추가해줍니다.

@Data
public class PostVO {
    private int id;
    private String title;
    private String content;
    private Timestamp created_date = new Timestamp(new Date().getTime());
    private UserVO user;
    private List<FileVO> fileList = new ArrayList<>();
}

ServletConfig 수정

전과 마찬가지로 로그인한 유저만 다운로드를 할 수 있도록 수정합니다.

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

PostMapper.xml 수정

resultMap 태그안 collection 태그가 SQL을 수행하여 연관된 파일들을 조회하여 PostVOfileList에 저장합니다. select속성이 실행할 SQL문이고 ofTypecollection에 들어갈 각 원소의 타입입니다. columnSQL을 실행할 때 WHERE문의 조건으로 들어갈 column을 뜻합니다.

<resultMap id="post" type="PostVO">
        <id property="id" column="id"/>
        <id property="title" column="title"/>
        <id property="content" column="content"/>
        <id property="created_date" column="created_date"/>
        <association property="user" javaType="UserVO">
            <result property="id" column="user_id"/>
            <result property="email" column="email"/>
            <result property="name" column="name"/>
        </association>
        <collection property="fileList" column="id" select="findFileListById" ofType="FileVO"/>
    </resultMap>

    <select id="findFileListById" resultType="FileVO">
        SELECT * FROM file WHERE post_id = #{id}
    </select>

FileMapper 인터페이스와 xml수정

idfile을 조회할 수 있도록 수정합니다.

public interface FileMapper {
    public void save(FileVO fileVO);
    public FileVO findById(int id);
}
<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>
    <select id="findById" resultType="FileVO">
        SELECT * FROM file WHERE id = #{id}
    </select>
</mapper>

FileVO 수정

getPath 메소드를 추가해 파일의 경로를 반환할 수 있도록 합니다.

@Data
@Builder
public class FileVO {
    public int id;
    public String name;
    public String uuid;
    public String uploadPath;
    public int postId;

    public String getPath(){
        return uploadPath + "/" + uuid + "_" + name;
    }
}

FileService 수정

파일 다운로드를 구현하는 방법은 여러가지 방법이 있습니다. 저희는 Spring에서 제공하는 클래스인 FileSystemResource를 사용해 구현해보도록 하겠습니다. FileVOgetPath 메소드를 이용하여 파일의 경로를 주어서 FileSystemResource 객체를 생성합니다. 만약 경로에 주어진 파일이 없다면 null을 반환합니다.

public interface FileService {
    public List<FileVO> saveAll(int postId,List<MultipartFile> files);
    public FileSystemResource getFileSystemResource(int id);
}
@Override
public FileSystemResource getFileSystemResource(int id) {
	FileSystemResource resource = new FileSystemResource(fileMapper.findById(id).getPath());

	if(!resource.exists())
            return null;

        return resource;
    }

FileController 수정

파일을 다운로드 할 수 있도록 수정합니다. 만약에 resourcenull이라면 404에러를 반환합니다. 브라우저가 첨부파일의 이름을 처리하는 방식이 다르기 때문에 HttpHeader에서 브라우저의 정보를 받아와 서로 다르게 처리해줍니다. HeaderUser-Agent가 브라우저의 정보를 담고있습니다. 파일을 다운로드 할때는 Content-DispositionHeader에 추가해주어야합니다.

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

    @GetMapping(value = "/{fileId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public ResponseEntity<FileSystemResource> getFile(@PathVariable("fileId") int fileId,
                                                      @RequestHeader("User-Agent") String userAgent){
        FileSystemResource resource = fileService.getFileSystemResource(fileId);

        if(resource==null)
            return ResponseEntity.notFound().build();

        String fileName = resource.getFilename();
        /*
		파일의 진짜 이름으로 만들기 위해 
		ex)4a08afec-1abf-4300-aea0-31fd082362ad_KakaoTalk_Photo_2021-03-27-10-33-45.jpeg에서
		첫번째 _를 찾아 잘라내면 KakaoTalk_Photo_2021-03-27-10-33-45.jpeg로
		파일의 실제 이름을 반환할 수 있다.
        */
        fileName = fileName.substring(fileName.indexOf("_")+1); 
        HttpHeaders headers = new HttpHeaders();

        try{
            if(userAgent.contains("Chrome"))
                fileName = new String(fileName.getBytes("UTF-8"),"ISO-8859-1");
            else
                fileName = URLEncoder.encode(fileName,"UTF-8");

            if(userAgent.contains("Safari"))
                headers.add("Content-Disposition", "attachment; filename*=utf-8''"+fileName);
            else
                headers.add("Content-Disposition","attachment; filename="+fileName);


        }catch (Exception e){e.printStackTrace();}

        return new ResponseEntity(resource,headers, HttpStatus.OK);
    }
    @PostMapping
    public void postFile(@RequestParam("files") List<MultipartFile> files, @PathVariable("postId") int postId){
        fileService.saveAll(postId,files);
    }
}

@GetMappingproduces 속성은 responseContent-Type을 뜻합니다.


테스트


0개의 댓글