首页 > 解决方案 > 如何将文件上传集成到 Spring Data REST 存储库中?

问题描述

我想将文件上传和下载集成到 Spring Data REST 存储库中。

我有一个DataFile类(见下面的实现),它总是伴随着一些元数据。有一个DataFileRepository存储有关元数据和文件之间关系的信息。

用户应该能够将文件与元数据一起上传到 web 应用程序表单中。元数据应该存储在数据库表中,同时文件被上传到某个文件存储,然后文件的路径再次存储在实体中。

我想使用 Spring Data REST 来做到这一点。我的方法是MultipartFileDataFile实体中包含 a 并标记它@Transient@RestResource(exported = false)因此它不在实体的任何表示中,但可以上传。上传时,在此步骤中将AbstractRepositoryEventListener覆盖并上传文件。onBeforeCreate

但是通过这种方法,我正在努力将文件包含在 HTTP 请求正文中(的 JSON 表示DataFile)。

其他方法将包括实现 a@RestRepositoryController以增强DataFileRepository但前面提到的方法是我最喜欢的。

使用 Spring Data REST(存储库)进行文件上传的最佳实践是什么?

@Data
@Entity
@Table(name = "data_files")
public class DataFile {
    @Id
    @GeneratedValue
    @Setter(AccessLevel.NONE)
    private long identifier;

    @NotBlank
    @Column(name = "path")
    @RestResource(exported = false)
    private String path;

    @Embedded
    private Metadata metadata;

    public DataFile() {
        metadata = new Metadata();
    }

    public DataFile(Metadata metadata) {
        this.metadata = metadata;
    }
}

标签: javaspringhttpfile-uploadspring-data-rest

解决方案


我没有找到只使用 Spring Data REST 的方法,但是这个解决方案很好地集成到了存储库中,并且不需要太多的手写代码。

以下是与问题之一不同的项目的此类解决方案的示例,但您应该明白这一点。

基本上创建一个@BasePathAwareController,以便它适应您可能为 Spring Data REST 设置的基本路径。然后使用适当的路径在控制器内部创建映射。在这种情况下,有@PostMapping一个到tutors存储库的路径和一个在此处上传的相关附件的子路径。

您可以在此方法中进行验证,也可以在存储库的保存方法中引入一个方面。

在这种情况下,我使用了一个方面将处理应用于save()存储库方法的每个调用。

@BasePathAwareController
@ExposesResourceFor(Tutor.class)
public class TutorRepositoryController {
    private AttachmentAssembler attachmentAssembler;

    private AttachmentRepository attachmentRepository;

    private TutorRepository tutorRepository;

    private RepositoryEntityLinks entityLinks;

    @Autowired
    public TutorRepositoryController(AttachmentAssembler attachmentAssembler,
                                     AttachmentRepository attachmentRepository,
                                     TutorRepository tutorRepository,
                                     RepositoryEntityLinks entityLinks) {
        this.attachmentAssembler  = attachmentAssembler;
        this.attachmentRepository = attachmentRepository;
        this.tutorRepository      = tutorRepository;
        this.entityLinks          = entityLinks;
    }

    @PostMapping(value = "/tutors/{id}/attachments/{name}")
    public ResponseEntity<EntityModel<Attachment>> saveAttachment(@RequestParam("data") MultipartFile file,
                                                                  @PathVariable long id,
                                                                  @PathVariable String name) {
        Tutor thisTutor = tutorRepository.findById(id);
        File tempFile;
        try {
            tempFile = File.createTempFile(FilenameUtils.getBaseName(file.getOriginalFilename()),
                                           FilenameUtils.getExtension(file.getOriginalFilename()));

            StreamUtils.copy(file.getResource().getInputStream(), new FileOutputStream(tempFile));
        } catch (IOException e) {
            throw new RuntimeException("Could not create temporary file of uploaded file.");
        }

        Attachment saved =
                    attachmentRepository.save(new Attachment(name, thisTutor, tempFile, file.getOriginalFilename()));

        return ResponseEntity.created(entityLinks.linkForItemResource(Attachment.class, saved.getIdentifier())
                                                  .toUri()).body(attachmentAssembler.toModel(saved));
    }
}

这是处理文件上传的方面的代码。

@Aspect
@Component
public class TutorRepositoryAspect {
    private Logger log = LoggerFactory.getLogger(TutorRepositoryAspect.class.getName());

    private AttachmentRepository attachmentRepository;

    private ApplicationConfiguration configuration;

    private S3Service s3Service;

    @Autowired
    public TutorRepositoryAspect(AttachmentRepository attachmentRepository,
                                 ApplicationConfiguration configuration,
                                 S3Service s3Service) {
        this.attachmentRepository = attachmentRepository;
        this.configuration        = configuration;
        this.s3Service            = s3Service;
    }

    @Pointcut(value = "execution(* com.example.tutor.backend.repository.attachment.AttachmentRepository.save(..)) && args(attachment)")
    private void repositorySave(Attachment attachment) {
    }

    @Before(value = "repositorySave(attachment)", argNames = "attachment")
    private Attachment beforeSave(Attachment attachment) {
        log.info("Received request to add attachment to tutor \"{}\".", attachment.getIdentifier());

        File file = attachment.getFile();

        // Validation

        String filePath = attachment.getAttachedTo().getIdentifier() + "-" + attachment.getName() + "-"
                          + attachment.getOriginalFileName();

        if (s3Service.isFilePresent(filePath)) {
            log.error("Could not upload file as there is a file already with the same name.");
            throw new IllegalArgumentException("File is already present.");
        }

        String s3Path = s3Service.uploadFile(file, filePath);

        attachment.setFilePath(s3Path);

        // This attachment is now being saved to database.
        return attachment;
    }
}

推荐阅读