Last Modified 2018.3.26

구글 클라우드 Blobstore 예제

예제 : https://github.com/kimjonghoon/gaefile
(에이젝스 파일 업로드 예제를 Blobstore를 이용하는 것으로 수정)

실행 방법

Google Cloud SDK 설치가 필요하다.
https://cloud.google.com/sdk/downloads를 방문한다.
'대화형 설치 프로그램'의 지시를 따라 설치한다.
(대화형 설치 프로그램으로 설치해야 최신 버전이 설치된다)
설치 후 명령 프롬프트를 새로 열고 gcloud components install app-engine-java를 실행해 자바 컴포넌트를 설치한다.

내려받은 예제를 풀고 루트 디렉터리로 이동하여 mvn appengine:run 실행한다.
(이때 자바는 자바 8이어야 한다)

명령 프롬프트에 Dev App Server is now running 메시지가 보이면, 웹 브라우저로 http://localhost:8080을 방문한다.

코드 설명

  • 게시글은 관계형 데이터베이스의 테이블에, 첨부파일 정보는 데이터스토어에, 첨부파일은 Blobstore에 저장된다고 가정했다.
  • index.jsp에서 1099는 게시글 번호를 의미한다.
  • FileGroup 엔티티는 AttachFile의 부모 엔티티다. 게시글이 엔티티로 저장된다면 이 엔티티는 필요 없다.
  • FileGroup 엔티티는 1099로 키가 만들어지므로 Id 속성 타입을 String으로 했다. Id 속성 타입이 Long이면 키는 자동 생성된다.
  • 다음 코드는 엔티티를 저장한다.
    (import static com.googlecode.objectify.ObjectifyService.ofy;문이 필요하다)
    ofy().save().entity(attachFile).now();
    
  • 다음은 목록을 구하는 코드다.
    order("creation")은 오름차순으로 정렬한다.
    Key<FileGroup> theGroup = Key.create(FileGroup.class, group);
    List<AttachFile> attachFiles = ofy()
    	.load()
    	.type(AttachFile.class)
    	.ancestor(theGroup)
    	.order("creation")
    	.list();
    
  • 다음은 AttachFile 엔티티와 파일을 삭제하는 핸들러 메소드 코드다.
    @DeleteMapping("/{group}/{id}")
    public ResponseEntity<String> del(@PathVariable String group, @PathVariable Long id) {
        Key<FileGroup> theGroup = Key.create(FileGroup.class, group);
        Key<AttachFile> key = Key.create(theGroup, AttachFile.class, id);
    
        AttachFile attachFile = ofy()
                .load()
                .key(key)
                .now();
    
        String blobKeyString = attachFile.getBlobKeyString();
        BlobKey blobKey = new BlobKey(blobKeyString);
        blobstoreService.delete(blobKey);
    
        ofy().delete().key(key).now();
    
        return new ResponseEntity<>("OK", HttpStatus.NO_CONTENT);
    }
    
    파일과 관련된 일은 모두 FileController가 담당하도록 구현했다.
    그래서 FileController를 @RestController로 구현할 수 없었다.
    반환 타입을 ResponseEntity<String> 둔 이유는 @Controller로 만든 컨트롤러는 어떻게든 뷰를 해석하려 들기 때문이다.
    위와 같이 구현하면 뷰를 무시하고 반환 타입을 지정할 수 있다.
  • 다음은 파일을 내려받게 하는 코드다.
    @GetMapping("/{group}/{id}")
    public void download(@PathVariable String group, @PathVariable Long id, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Key<FileGroup> theGroup = Key.create(FileGroup.class, group);
        Key<AttachFile> key = Key.create(theGroup, AttachFile.class, id);
    
        AttachFile attachFile = ofy()
                .load()
                .key(key)
                .now();
    
        String blobKeyString = attachFile.getBlobKeyString();
        BlobKey blobKey = new BlobKey(blobKeyString);
        BlobInfo blobInfo = new BlobInfoFactory().loadBlobInfo(blobKey);
        String filename = URLEncoder.encode(blobInfo.getFilename(), "UTF-8");
        resp.setContentType("application/octet-stream");
        resp.addHeader("Content-Disposition", "attachment; filename*=UTF-8''" + filename);
        blobstoreService.serve(blobKey, resp);
     }