我有一个整体的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);
}
答案 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}
}
我同时拥有filename
和originalFilename
,因为我的要求之一是保留用户上传文件时使用的任何文件名。我在服务器端使用的生成的唯一名称对用户是透明的。
一旦使用类似的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
中配置的文件夹中,该文件夹按年和月分隔在子目录中。这是为了避免在同一文件夹中存储太多文件,这可能会让人头疼。
我相信很多事情都可以做得更好,但这对我有用。