如何将文件上传集成到 Spring 数据 REST 存储库中?

How to integrate a file upload into Spring Data REST repositories?

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

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

用户应该能够上传网络应用程序形式的文件和元数据。元数据应存储在数据库表中,同时将文件上传到某个文件存储,然后将文件的路径再次存储在实体中。

我想使用 Spring Data REST 来做到这一点。我的方法是在 DataFile 实体中包含一个 MultipartFile 并将其标记为 @Transient@RestResource(exported = false) 因此它不代表实体但可以上传。 上传后 AbstractRepositoryEventListener 会覆盖 onBeforeCreate 并在此步骤中上传文件。

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

其他方法包括实施 @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;
    }
}

我没有找到仅使用 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;
    }
}