화면에서 파일을 클릭하면 다운로드를 받고 싶을 때

view 화면

      <div id="downloadList" class="mt-1 text-center">
          <span class="attachment" th:each="a : *{attachments}">
            <input type="hidden" name="attachments" th:value="${a.id}" th:field="*{attachments}"/>
            <a class="text-black" th:href="|@{/attachment/}${a.id}|">
              <div class="file-name" th:text="${a.fileName}"></div>
            </a>
          </span>
      </div>

 

연결이 /attachment/{id}로 되어 있으니 controller를 생성

AttachmentController

@RestController
@RequestMapping("/attachment")
@RequiredArgsConstructor
public class AttachmentController {

  @NonNull
  private final AttachmentService attachmentService;

  @GetMapping(value = "/{id}", produces = "application/octet-stream")
  public ResponseEntity attachment(@PathVariable("id") Long id) throws IOException {
    Attachment attachment = attachmentService.findById(id).orElseThrow(DataNotFoundException::new);
    File file = new File(attachment.getFilePath());

    if (file.exists() && file.length() > 0) {
      byte[] bytes = FileUtils.readFileToByteArray(file);

      HttpHeaders httpHeaders = new HttpHeaders();
      httpHeaders.setContentLength(bytes.length);
      httpHeaders.set("Content-Disposition",
          "attachment; filename=\"" + attachment.getEncodedFileName() + "\";");
      httpHeaders.set("Content-Transfer-Encoding", "binary");
      httpHeaders.setContentType(MediaType.valueOf(Files.probeContentType(file.toPath())));
      return new ResponseEntity(bytes, httpHeaders, HttpStatus.OK);
    }

    return ResponseEntity.notFound().build();
  }
}

 

해당 controller 함수에 맞게 repository랑 service 구현

AttachmentRepository

@Repository
public interface AttachmentRepository extends JpaRepository<Attachment, Long> {

}

 

AttachmentService

@Service
@RequiredArgsConstructor
public class AttachmentService {

  @NonNull
  private final AttachmentRepository attachmentRepository;

  @Cacheable(value = "attachment", key = "#id")
  public Optional<Attachment> findById(Long id) {
    return attachmentRepository.findById(id);
  }

  @CacheEvict(value = "attachment", key = "#attachment.id")
  public Attachment save(Attachment attachment) {
    return attachmentRepository.save(attachment);
  }
}

 

위의 과정을 거치면 파일 클릭시 다운로드 가능!!

domain 추가

(첨부파일: Attachment)

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "attachment")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Attachment {

  public Attachment(Long id) {
    this.id = id;
  }

  public Attachment(String fileName, String filePath, long fileSize, String fileContentType) {
    this.fileName = fileName;
    this.filePath = filePath;
    this.fileSize = fileSize;
    this.fileContentType = fileContentType;
  }

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @EqualsAndHashCode.Include
  private Long id;

  @Column(updatable = false, nullable = false)
  private String fileName; // 원본 파일명

  @Column(updatable = false, nullable = false, unique = true)
  private String filePath; // 실제 저장된 파일명

  @Column(updatable = false)
  private String fileContentType;

  @Column(updatable = false)
  private long fileSize;

  @PreRemove
  public void onRemove() {
    File file = new File(filePath);
    FileUtils.deleteQuietly(file);
  }

  @JsonIgnore
  public String getEncodedFileName() throws UnsupportedEncodingException {
    return URLEncoder.encode(this.fileName, "UTF-8").replace("+", "%20");
  }

  public String   getFileSizeText() {
    float size = fileSize;
    String unit = "Byte";
    if (size >= 1024) {
      unit = "KB";
      size /= 1024;
      if (size >= 1024) {
        unit = "MB";
        size /= 1024;
      }
    }
    return String.format("%d%s", (int) size, unit);
  }

  public String getIconPath() {
    String type = "file";
    if(fileContentType.toLowerCase().startsWith("image")) {
      type = "img";
    } else {
      String lowerCaseName = fileName.toLowerCase();
      if(lowerCaseName.endsWith("doc") || lowerCaseName.endsWith("docx")) {
        type = "doc";
      } else if(lowerCaseName.endsWith("hwp")) {
        type = "hwp";
      } else if(lowerCaseName.endsWith("pdf")) {
        type = "pdf";
      } else if(lowerCaseName.endsWith("xls") || lowerCaseName.endsWith("xlsx")) {
        type = "xl";
      } else if(lowerCaseName.endsWith("zip")) {
        type = "zip";
      } else if(lowerCaseName.endsWith("ppt") || lowerCaseName.endsWith("pptx")) {
        type = "ppt";
      }
    }
    return "static/img/files/" + String.format("icon-%s.png", type);
  }
}

 

service추가

(파일 업로드 관련)

@Service
@RequiredArgsConstructor
public class FileUploadService {

  public static final String PATH_LOGOS = "logos/";

  public static final String PATH_ATTACHMENTS = "attachments/";

  @Value("${spring.servlet.multipart.location}")
  private String rootPath;

  public Attachment upload(MultipartFile file, String destinationPath) throws IOException {
    if (file == null || file.isEmpty()) {
      return null;
    }

    String fileName = file.getOriginalFilename();
    String fileExtension = FilenameUtils.getExtension(fileName);
    if (StringUtils.isBlank(fileExtension)) {
      return null;
    }

    File destinationFile;
    String destinationFileName;
    do {
      destinationFileName = destinationPath + FileUtil.getRandomFilename(fileExtension);
      destinationFile = new File(rootPath + destinationFileName);
    } while (destinationFile.exists());

    //noinspection ResultOfMethodCallIgnored
    destinationFile.getParentFile().mkdirs();
    file.transferTo(destinationFile);

    Attachment attachment = new Attachment();

    //이미지인 경우 리사이징
    if (file.getContentType() != null && file.getContentType().contains("image")) {
      BufferedImage image = ImageIO.read(destinationFile);

      double maxThumbnailWidth = 1280.0;
      double thumbnailRatio =
          maxThumbnailWidth > image.getWidth() ? 1 : maxThumbnailWidth / image.getWidth();

      //이미지 사이즈 변경
      BufferedImage resizedImage = resize(image, thumbnailRatio, Image.SCALE_FAST);
      ImageIO.write(resizedImage, "JPEG", destinationFile);
    }

    attachment.setFileName(file.getOriginalFilename());
    attachment.setFilePath(FilenameUtils.normalize(destinationFile.getAbsolutePath(), true));
    attachment.setFileContentType(file.getContentType());
    attachment.setFileSize(file.getSize());
    return attachment;
  }

  public Attachment uploadFile(MultipartFile file) throws IOException {
    return upload(file, PATH_ATTACHMENTS);
  }

  public Attachment uploadLogo(MultipartFile file) throws IOException {
    return upload(file, PATH_LOGOS);
  }

  private BufferedImage resize(BufferedImage beforeImage, double ratio, int imageScale) {
    int newW = (int) (beforeImage.getWidth() * ratio);
    int newH = (int) (beforeImage.getHeight() * ratio);
    Image tmp = beforeImage.getScaledInstance(newW, newH, imageScale);
    BufferedImage dimg = new BufferedImage(newW, newH, beforeImage.getType());

    Graphics2D g2d = dimg.createGraphics();
    g2d.drawImage(tmp, 0, 0, null);
    g2d.dispose();

    return dimg;
  }
}

 

파일을 첨부할 controller 수정

 

  @PostMapping("/save")
  public String save(@ModelAttribute("project") Project project, BindingResult result,
      MultipartFile[] files) {

    if (!result.hasErrors()) {
      if (files != null) {
        for (MultipartFile file : files) {
          try {
            project.getAttachments().add(fileUploadService.uploadFile(file));
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
      projectService.save(project);
      return "project/edit";
    }
    return null;
  }

 

화면에 폼 추가하기

(project/edit)

<form method="post" th:action="|@{/project/save}|" th:object="${project}" enctype="multipart/form-data">
 <!--        첨부파일-->
 <div class="d-flex mt-3 mb-2 justify-content-between">
 	<label class="form-label m-0 mr-2">Attachment</label>
 	<div>
 		<label for="input-file" id="addFile" class="input-file-button">Upload</label>
 		<input type="file" id="input-file" name="files" style="display: none;" multiple/>
 	</div>
 </div>
 </form>

enctype="multipart/form-data" 이게 중요하다! 이걸 표시해줘야 파일이란 것을 알 수 있음!!

 

본인의 프로젝트에 맞게 설정을 잘하고 저장버튼을 누르면 파일이 업로드 됨을 볼 수 있다.

DB에 추가됨

'Web > Spring' 카테고리의 다른 글

[Login구현] login-logout구현(setting)  (0) 2021.08.10
[MultipartFile] 첨부파일 다운로드  (0) 2021.08.06
[properties] server.port vs server.http.port  (0) 2021.08.06
[ERROR] 유의해야 할 점  (0) 2021.08.06
[Annotation] @PathVariable  (0) 2021.08.06

+ Recent posts