在 JHipster 中实现文件附件
Implementing file attachments in JHipster
我有一个单体 JHipster (v5.6.1) 项目,我需要实现一个实体的文件附件。
我不知道从哪里开始。 DTO 被发送到 REST 控制器以创建或更新它们,我应该如何发送文件?
我可以将 Base64 附加到我的 DTO 并以这种方式发送,但我不确定如何(是否有任何 angular 插件来完成此操作?)。这将需要对服务器进行额外的工作,并且我认为需要额外的文件大小。
另一种选择是分别发送实体 (DTO) 和文件,同样我不太确定如何。
我认为没有理由不回答这个问题,因为我现在已经在我的几个项目中成功实施了附件。
如果您愿意,可以跳过此解释并查看我在 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 的映射,在服务器上查找关联的文件,然后 returns 该文件供浏览器下载。在您的实体详细信息视图中使用此组件:
<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
中配置的文件夹中,按年和月分隔在子目录中。这是为了避免在同一个文件夹中存储太多文件,这会让人很头疼。
我确信很多事情可以做得更好,但这对我有用。
我有一个单体 JHipster (v5.6.1) 项目,我需要实现一个实体的文件附件。
我不知道从哪里开始。 DTO 被发送到 REST 控制器以创建或更新它们,我应该如何发送文件?
我可以将 Base64 附加到我的 DTO 并以这种方式发送,但我不确定如何(是否有任何 angular 插件来完成此操作?)。这将需要对服务器进行额外的工作,并且我认为需要额外的文件大小。
另一种选择是分别发送实体 (DTO) 和文件,同样我不太确定如何。
我认为没有理由不回答这个问题,因为我现在已经在我的几个项目中成功实施了附件。
如果您愿意,可以跳过此解释并查看我在 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 的映射,在服务器上查找关联的文件,然后 returns 该文件供浏览器下载。在您的实体详细信息视图中使用此组件:
<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
中配置的文件夹中,按年和月分隔在子目录中。这是为了避免在同一个文件夹中存储太多文件,这会让人很头疼。
我确信很多事情可以做得更好,但这对我有用。