在JHipster中实现文件附件(Angular7 + Spring)

时间:2018-11-14 16:31:22

标签: spring angular jhipster

我有一个整体的JHipster(v5.6.1)项目,我需要实现实体的文件附件。

我不知道从哪里开始。 DTO发送到REST控制器以创建或更新它们,我应该如何发送文件?

我可以将Base64附加到我的DTO并以这种方式发送,但是我不确定如何(有没有棱角分明的插件可以做到这一点?)。这将需要服务器额外的工作,并且我认为还需要额外的文件大小。

另一种选择是分别发送实体(DTO)和文件,我也不完全确定该怎么做。

非常感谢,请原谅我对SO不太了解的不良格式:)

更新

这是当前外观(香草Jhipster 5):

sample-update.component.html

<form name="editForm" role="form" novalidate (ngSubmit)="save()" #editForm="ngForm">
    <!-- DTO fields -->
    <div class="form-group">
      <!-- To be developed, linked to 'sample' model or separate? -->
      <input type="file" name="file" id="field_file" />
    </div>
    <!-- The rest of the form -->
</form>

sample-update.component.ts

@Component({
    selector: 'jhi-sample-update',
    templateUrl: './sample-update.component.html'
})
export class SampleUpdateComponent implements OnInit {
    // Variables, constructor, onInit(), ...

    save() {
        this.isSaving = true;
        if (this.sample.id !== undefined) {
            this.subscribeToSaveResponse(this.sampleService.update(this.sample));
        } else {
            this.subscribeToSaveResponse(this.sampleService.create(this.sample));
        }
    }

sample.service.ts

@Injectable({ providedIn: 'root' })
export class SampleService {
    public resourceUrl = SERVER_API_URL + 'api/sample';

    constructor(private http: HttpClient) {}

    create(sample: ISample): Observable<EntityResponseType> {
        const copy = this.convertDateFromClient(sample);
        return this.http
            .post<ISample>(this.resourceUrl, copy, { observe: 'response' })
            .pipe(map((res: EntityResponseType) => this.convertDateFromServer(res)));
    }

SampleResource.java

@RestController
@RequestMapping("/api")
public class SampleResource {

    // Attributes and constructor

    @PostMapping("/samples")
    @Timed
    public ResponseEntity<SampleDTO> createSample(@Valid @RequestBody SampleDTO sampleDTO) throws URISyntaxException {
        log.debug("REST request to save Sample : {}", sampleDTO);
        if (sampleDTO.getId() != null) {
            throw new BadRequestAlertException("A new sample cannot already have an ID", ENTITY_NAME, "idexists");
        }
        SampleDTO result = sampleService.save(sampleDTO);
        return ResponseEntity.created(new URI("/api/samples/" + result.getId()))
            .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
            .body(result);
    }

1 个答案:

答案 0 :(得分:0)

由于我已经在多个项目中成功实现了附件,因此我没有理由不回答这个问题。

如果愿意,可以跳过此说明并检查我在vicpermir/jhipster-ng-attachments创建的github存储库。这是一个工作项目,随时可以启动并播放。

通常的想法是让一个Attachment实体具有所有必填字段(文件大小,名称,内容类型等),并为您要设置的任何实体设置many-to-many关系实现文件附件。

JDL是这样的:

// Test entity, just to showcase attachments, this should
// be the entity you want to add attachments to
entity Report {
    name String required
}

entity Attachment {
    filename String required            // Generated unique filename on the server
    originalFilename String required    // Original filename on the users computer
    extension String required
    sizeInBytes Integer required
    sha256 String required              // Can be useful for duplication and integrity checks
    contentType String required
    uploadDate Instant required
}

// ManyToMany instead of OneToMany because it will be easier and cleaner
// to integrate attachments into other entities in case we need to do it
relationship ManyToMany {
    Report{attachments} to Attachment{reports}
}

我同时拥有filenameoriginalFilename,因为我的要求之一是保留用户上传文件时使用的任何文件名。我在服务器端使用的生成的唯一名称对用户是透明的。

一旦使用类似的JDL生成项目,就必须将文件有效负载添加到DTO(如果不使用DTO则为实体),以便服务器可以在base64中接收它,并且储存它。

我的AttachmentDTO中有这个:

...
    private Instant uploadDate;

    // File payload (transient)
    private byte[] file;

    public Long getId() {
        return id;
    }
...

然后,您只需要在服务器端处理这些字节数组,存储它们,并将对该位置的引用保存到数据库中即可。

AttachmentService.java

    /**
     * Process file attachments
     */
    public Set<AttachmentDTO> processAttachments(Set<AttachmentDTO> attachments) {
        Set<AttachmentDTO> result = new HashSet<>();
        if (attachments != null && attachments.size() > 0) {
            for (AttachmentDTO a : attachments) {
                if (a.getId() == null) {
                    Optional<AttachmentDTO> existingAttachment = this.findBySha256(a.getSha256());
                    if(existingAttachment.isPresent()) {
                        a.setId(existingAttachment.get().getId());
                    } else {
                        String fileExtension = FilenameUtils.getExtension(a.getOriginalFilename());
                        String fileName = UUID.randomUUID() + "." + fileExtension;
                        if (StringUtils.isBlank(a.getContentType())) {
                            a.setContentType("application/octet-stream");
                        }
                        Boolean saved = this.createBase64File(fileName, a.getFile());
                        if (saved) {
                            a.setFilename(fileName);
                        }
                    }
                }
                result.add(a);
            }
        }
        return result;
    }

我在这里要做的是检查附件是否已经存在(使用SHA256哈希)。如果可以,我将使用该文件,否则将存储新文件并保留新的附件数据。

现在剩下的是在客户端管理附件。我为此创建了两个组件,因此将附件添加到新实体非常简单。

attachment-download.component.ts

...
@Component({
  selector: 'jhi-attachment-download',
  template: , // Removed to reduce verbosity
  providers: [JhiDataUtils]
})
export class JhiAttachmentDownloadComponent {
  @Input()
  attachments: IAttachment[] = [];
}

这只是调用一个映射,该映射获取附件ID,在服务器上查找关联的文件,然后将该文件返回给浏览器下载。在您的实体详细信息视图中将此组件用于:

<jhi-attachment-download [attachments]="[your_entity].attachments"></jhi-attachment-download>

attachment-upload.component.ts

...
@Component({
  selector: 'jhi-attachment-upload',
  template: , // Removed to reduce verbosity
  providers: [JhiDataUtils]
})
export class JhiAttachmentUploadComponent {
  @Input()
  attachments: IAttachment[] = [];
  loadingFiles: number;

  constructor(private dataUtils: JhiDataUtils) {
    this.loadingFiles = 0;
  }

  addAttachment(e: any): void {
    this.loadingFiles = 0;
    if (e && e.target.files) {
      this.loadingFiles = e.target.files.length;
      for (let i = 0; i < this.loadingFiles; i++) {
        const file = e.target.files[i];
        const fileName = file.name;
        const attachment: IAttachment = {
          originalFilename: fileName,
          contentType: file.type,
          sizeInBytes: file.size,
          extension: this.getExtension(fileName),
          processing: true
        };
        this.attachments.push(attachment);
        this.dataUtils.toBase64(file, (base64Data: any) => {
          attachment.file = base64Data;
          attachment.sha256 = hash
            .sha256()
            .update(base64Data)
            .digest('hex');
          attachment.processing = false;
          this.loadingFiles--;
        });
      }
    }
    e.target.value = '';
  }

  getExtension(fileName: string): string {
    return fileName.substring(fileName.lastIndexOf('.'));
  }
}

在您的实体更新视图中使用以下组件:

<jhi-attachment-upload [attachments]="editForm.get('attachments')!.value"></jhi-attachment-upload>

文件发送到服务器后,将存储在您在application-*.yml中配置的文件夹中,该文件夹按年和月分隔在子目录中。这是为了避免在同一文件夹中存储太多文件,这可能会让人头疼。

我相信很多事情都可以做得更好,但这对我有用。