연극 이벤트 문서화 시스템
이벤트 관리에서 문서는 청구서, 계약서, 계약서 등 중요한 자산을 나타냅니다. 허가증, 포스터, 인증서 및 영수증을 정리하고 버전을 지정하고 공유해야 합니다. 안전하게. ~ 안에 이벤트를 플레이하세요, 문서 모듈은 Domain-Driven Design의 원칙에 따라 설계되었습니다. 와 이벤트문서 버전 관리, 분류를 조정하는 Aggregate Root와 같은 자동, 메타데이터 추출 및 임시 링크를 통한 공유.
이 접근 방식은 데이터 무결성, 변경 사항의 완전한 추적성을 보장합니다. MIME 유형 및 카테고리에 따라 파일을 논리적 폴더로 자동 구성 문서의.
이 기사에서 찾을 수 있는 내용
- 집계 루트
DocumentoEvento문서의 DDD 패턴 - 사전 정의된 11개의 카테고리와 이벤트별 맞춤 카테고리
- MIME 유형 및 카테고리를 기반으로 폴더 자동 감지
- 버전을 추가하고 복원하는 버전 관리 시스템
- 시간 제한 및 다운로드 제한이 있는 링크 공유
- 자동 메타데이터 추출(EXIF, PDF, Office)
- SHA-256 해싱을 통한 파일 무결성
- 서류-비용 연결 및 정리된 보관
EventDocument: 집계 루트
DocumentoEvento 이는 제한된 문서 컨텍스트의 집계 루트입니다. 매
문서는 특정 이벤트에 속하며 사용자가 업로드하여 내부적으로 관리됩니다.
버전 기록 및 공유 링크. 팩토리 메소드 crea()
첫 번째 버전이 이미 생성된 상태에서 각 문서가 유효한 상태로 생성되도록 합니다.
@Entity
@Table(name = "documenti_evento")
public class DocumentoEvento {
private static final long MAX_DIMENSIONE_BYTES = 50L * 1024 * 1024; // 50MB
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long eventoId;
private String titolo; // max 200 caratteri
private String descrizione; // max 1000 caratteri
@Enumerated(EnumType.STRING)
private CategoriaDocumento categoria;
private Long categoriaPersonalizzataId;
@Enumerated(EnumType.STRING)
private TipoCartella tipoCartella;
private Integer versioneCorrente;
private String nomeFileOriginale;
private String mimeType;
private Long dimensioneBytes;
private Long caricatoDaId;
private Long spesaId; // collegamento opzionale a spesa
@Convert(converter = JsonMapConverter.class)
private Map<String, Object> metadati = new HashMap<>();
@OneToMany(mappedBy = "documento", cascade = CascadeType.ALL)
@OrderBy("numeroVersione DESC")
private List<VersioneDocumento> versioni = new ArrayList<>();
@OneToMany(mappedBy = "documento", cascade = CascadeType.ALL)
private Set<LinkCondivisioneDocumento> linkCondivisione = new HashSet<>();
}
테이블 documenti_evento 5개의 인덱스를 사용하여 쿼리 최적화
가장 자주 발생함: 이벤트별, 카테고리별, 폴더 유형별, 사용자별
문서를 업로드하고 관련 비용을 지불했습니다. 다음을 통한 낙관적 잠금 @Version
동시 업데이트 충돌을 방지합니다.
전체 검증이 포함된 팩토리 메서드
건축업자는 protected 팩토리 메소드를 통해서만 접근 가능
crea(), 인스턴스를 생성하기 전에 모든 매개변수의 유효성을 검사합니다. 폴더 유형
MIME 유형 및 카테고리에 따라 자동으로 결정됩니다.
public static DocumentoEvento crea(
Long eventoId, String titolo, String descrizione,
CategoriaDocumento categoria, Long categoriaPersonalizzataId,
String nomeFileOriginale, String mimeType,
Long dimensioneBytes, Long caricatoDaId,
String percorsoStorage, String hashContenuto) {
validaParametriCreazione(eventoId, titolo, nomeFileOriginale,
mimeType, dimensioneBytes, caricatoDaId, percorsoStorage);
DocumentoEvento doc = new DocumentoEvento();
doc.eventoId = eventoId;
doc.titolo = titolo.trim();
doc.categoria = categoria != null ? categoria : CategoriaDocumento.ALTRO;
doc.categoriaPersonalizzataId =
doc.categoria == CategoriaDocumento.PERSONALIZZATA
? categoriaPersonalizzataId : null;
doc.tipoCartella = TipoCartella.fromMimeTypeAndCategoria(mimeType, doc.categoria);
doc.versioneCorrente = 1;
// Crea la prima versione con relazione bidirezionale
VersioneDocumento primaVersione = VersioneDocumento.crea(
1, nomeFileOriginale, percorsoStorage,
dimensioneBytes, mimeType, hashContenuto,
caricatoDaId, "Versione iniziale"
);
primaVersione.setDocumento(doc);
doc.versioni.add(primaVersione);
return doc;
}
11가지 기본 카테고리
업로드된 모든 문서
이벤트를 플레이하세요
열거형에 미리 정의된 11개의 카테고리 중 하나와 연결되어 있습니다. CategoriaDocumento.
각 범주에는 사용자 인터페이스에 대해 표시 가능한 이름과 아이콘이 포함되어 있습니다.
public enum CategoriaDocumento {
FATTURA("Fattura", "file-text"),
SCONTRINO("Scontrino", "receipt"),
PERMESSO("Permesso", "shield-check"),
BANDO("Bando", "megaphone"),
CONTRATTO("Contratto", "file-signature"),
PREVENTIVO("Preventivo", "calculator"),
ASSICURAZIONE("Assicurazione", "shield"),
CERTIFICATO("Certificato", "award"),
LOCANDINA("Locandina", "image"),
PERSONALIZZATA("Categoria Personalizzata", "tag"),
ALTRO("Altro", "file");
private final String displayName;
private final String icona;
// Verifica se la categoria rappresenta una ricevuta
public boolean isRicevuta() {
return this == FATTURA || this == SCONTRINO;
}
// Suggerisce una categoria di default per un tipo MIME
public static CategoriaDocumento fromMimeType(String mimeType) {
if (mimeType != null && mimeType.startsWith("image/")) {
return LOCANDINA;
}
return ALTRO;
}
}
카테고리는 이벤트 관리의 일반적인 요구 사항을 다룹니다: 세금 문서
(송장, 영수증, 예방법),
법률 문서(계약, 허용하다, 알아채다,
보험), 자격 서류 (자격증) 전자
홍보자료(포스터). 방법 isRicevuta()
영수증 폴더로 자동 라우팅하기 위한 송장 및 영수증을 식별합니다.
맞춤형 이벤트 카테고리
사전 정의된 11개의 카테고리가 충분하지 않은 경우 주최자는 다음을 만들 수 있습니다.
맞춤 카테고리 귀하의 이벤트에 특정합니다. 엔터티
CategoriaDocumentoPersonalizzata 이름, 설명,
각 추가 카테고리의 아이콘, 색상 및 표시 순서입니다.
@Entity
@Table(name = "categorie_documento_evento",
uniqueConstraints = @UniqueConstraint(
name = "uq_categoria_evento_nome",
columnNames = {"evento_id", "nome"}
))
public class CategoriaDocumentoPersonalizzata {
private static final Pattern HEX_COLOR_PATTERN =
Pattern.compile("^#[0-9A-Fa-f]{6}$");
private static final String COLORE_DEFAULT = "#6b7280";
private static final String ICONA_DEFAULT = "file-text";
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long eventoId;
private String nome; // max 50 caratteri, unico per evento
private String descrizione; // max 200 caratteri
private String icona; // nome icona Lucide
private String colore; // formato hex #RRGGBB
private Integer ordine; // ordine di visualizzazione
}
고유성 제약 uq_categoria_evento_nome 생성을 방지
동일한 이벤트 내의 중복 카테고리. 색상은 다음을 통해 확인됩니다.
16진수 형식만 허용하는 정규식 #RRGGBB. 그 사람이 안 오면
색상이나 아이콘을 지정하면 기본값이 적용됩니다.
문서가 카테고리를 사용하는 경우 PERSONALIZZATA, 필드
categoriaPersonalizzataId ~의 DocumentoEvento 설정되었습니다
해당 맞춤 카테고리 ID를 사용하세요. 카테고리가 아닌 경우
PERSONALIZZATA을 클릭하면 해당 필드가 자동으로 지워집니다.
폴더 자동 감지
시스템은 MIME 유형에 따라 파일을 세 개의 논리적 폴더로 자동 구성합니다.
파일의 종류와 문서의 카테고리. 열거형 TipoCartella 정의합니다
라우팅 논리.
public enum TipoCartella {
DOCUMENTI("documenti"),
IMMAGINI("immagini"),
RICEVUTE("ricevute");
private final String percorso;
public static TipoCartella fromMimeTypeAndCategoria(
String mimeType, CategoriaDocumento categoria) {
// 1. Fatture e scontrini vanno SEMPRE nella cartella ricevute
if (categoria != null && categoria.isRicevuta()) {
return RICEVUTE;
}
// 2. I file con MIME type image/* vanno nella cartella immagini
if (mimeType != null && mimeType.startsWith("image/")) {
return IMMAGINI;
}
// 3. Tutto il resto va nella cartella documenti
return DOCUMENTI;
}
}
라우팅 우선순위는 명확합니다. 범주 하
MIME 유형보다 우선순위가 높습니다. PDF 형식의 송장이 폴더에 저장됩니다.
/ricevute 그리고 안에는 없어 /documenti, 해당 카테고리 때문에
(FATTURA)는 수신된 것으로 식별합니다. 분류되지 않은 JPEG 이미지
영수증 사양이 폴더에 있습니다. /immagini.
라우팅 규칙
- /영수증 - INVOICE 또는 RECEIPT 카테고리의 문서(모든 파일 형식)
- /이미지 - MIME 유형의 파일
image/*(JPEG, PNG, WebP 등) - /서류 - 기타 모든 파일(PDF, Word, Excel, 텍스트 등)
버전 관리 시스템
각 문서는 완전한 버전 기록을 유지합니다. 사용자가 업로드할 때
새 버전에서는 시스템이 카운터를 증가시킵니다. versioneCorrente 전자
새로운 객체를 생성합니다 VersioneDocumento 업데이트된 파일의 모든 메타데이터를 포함합니다.
@Entity
@Table(name = "versioni_documento",
uniqueConstraints = @UniqueConstraint(
name = "uq_documento_versione",
columnNames = {"documento_id", "numero_versione"}
))
public class VersioneDocumento {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "documento_id", nullable = false)
private DocumentoEvento documento;
private Integer numeroVersione; // progressivo, >= 1
private String nomeFileOriginale; // max 255 caratteri
private String percorsoStorage; // max 500 caratteri
private Long dimensioneBytes;
private String mimeType;
private String hashContenuto; // SHA-256, 64 caratteri hex
private Long creatoDaId;
private String notaVersione; // max 500 caratteri
private Instant creatoIl;
}
새 버전 추가
버전 추가는 Aggregate Root에 의해 전적으로 처리됩니다. 내부 상태의 일관성. 버전 번호가 증가합니다. 자동으로 기본 문서의 메타데이터가 다음의 데이터로 업데이트됩니다. 새 파일.
public VersioneDocumento aggiungiVersione(
String nomeFileOriginale, String percorsoStorage,
Long dimensioneBytes, String mimeType,
String hashContenuto, Long caricatoDaId, String notaVersione) {
validaParametriVersione(nomeFileOriginale, percorsoStorage,
dimensioneBytes, mimeType, caricatoDaId);
this.versioneCorrente++;
this.nomeFileOriginale = nomeFileOriginale;
this.mimeType = mimeType;
this.dimensioneBytes = dimensioneBytes;
VersioneDocumento nuovaVersione = VersioneDocumento.crea(
this.versioneCorrente, nomeFileOriginale, percorsoStorage,
dimensioneBytes, mimeType, hashContenuto,
caricatoDaId, notaVersione
);
nuovaVersione.setDocumento(this);
this.versioni.add(nuovaVersione);
return nuovaVersione;
}
이전 버전 복원
복원하면 기록을 덮어쓰지 않고 기록을 생성합니다. 새 버전 와 복원할 버전의 내용입니다. 이런 식으로 역사는 불변으로 남아있다. 모든 작업이 추적됩니다.
public VersioneDocumento ripristinaVersione(Integer numeroVersione, Long utenteId) {
VersioneDocumento versioneDaRipristinare = versioni.stream()
.filter(v -> v.getNumeroVersione().equals(numeroVersione))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(
"Versione " + numeroVersione + " non trovata"
));
// Crea una NUOVA versione con i dati della versione precedente
return aggiungiVersione(
versioneDaRipristinare.getNomeFileOriginale(),
versioneDaRipristinare.getPercorsoStorage(),
versioneDaRipristinare.getDimensioneBytes(),
versioneDaRipristinare.getMimeType(),
versioneDaRipristinare.getHashContenuto(),
utenteId,
"Ripristino versione " + numeroVersione
);
}
버전 관리 불변
이전 버전을 복원해도 기록이 삭제되거나 변경되지 않습니다. 기존. 문서에 3가지 버전이 있고 버전 1로 되돌리면 결과는 버전 1과 내용이 동일한 버전 4가 될 것입니다. 이 접근 방식은 완전한 감사 추적을 보장하고 우발적인 데이터 손실을 방지합니다.
임시 공유 링크
필요하지 않은 임시 링크를 통해 문서를 외부에 공유할 수 있습니다.
인증. 엔터티 LinkCondivisioneDocumento 세대를 관리하고,
이러한 링크의 유효성 검사 및 취소.
@Entity
@Table(name = "link_condivisione_documento")
public class LinkCondivisioneDocumento {
private static final Duration DURATA_MASSIMA = Duration.ofDays(30);
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "documento_id", nullable = false)
private DocumentoEvento documento;
private String token; // 64 caratteri, UUID-based, unico
private Instant scadenza; // max 30 giorni dalla creazione
private Long creatoDaId;
private Integer numeroDownload; // contatore incrementale
private Integer maxDownload; // limite opzionale (null = illimitato)
private Boolean revocato;
private Instant revocatoIl;
}
다중 조건 유효성
링크는 다음을 만족하는 경우에만 유효한 것으로 간주됩니다. 세 가지 동시 조건: 취소되지 않았고, 만료되지 않았으며, 최대 다운로드 한도에 도달하지 않았습니다.
public boolean isValido() {
// Condizione 1: non revocato manualmente
if (revocato) {
return false;
}
// Condizione 2: non scaduto temporalmente
if (Instant.now().isAfter(scadenza)) {
return false;
}
// Condizione 3: download rimanenti disponibili
if (maxDownload != null && numeroDownload >= maxDownload) {
return false;
}
return true;
}
public void registraDownload() {
if (!isValido()) {
throw new IllegalStateException(
"Impossibile registrare download: link non valido");
}
this.numeroDownload++;
}
토큰은 두 개의 UUID를 결합하고 64자로 줄여 생성됩니다. 독특함과 예측불가능성. 링크에 허용되는 최대 기간은 다음과 같습니다. 의 30일. 취소는 멱등성 작업입니다. 이미 취소된 링크는 오류를 생성하지 않습니다.
집계 루트에서 생성 및 취소
링크의 생성과 취소는 항상 Aggregate Root를 통과합니다. 문서와 관련된 링크 세트를 완벽하게 제어합니다.
// Generazione di un nuovo link temporaneo
public LinkCondivisioneDocumento generaLinkCondivisione(
Duration durata, Long creatoDaId, Integer maxDownload) {
LinkCondivisioneDocumento link = LinkCondivisioneDocumento.crea(
durata, creatoDaId, maxDownload
);
link.setDocumento(this);
this.linkCondivisione.add(link);
return link;
}
// Revoca di un link specifico tramite token
public void revocaLinkCondivisione(String token) {
LinkCondivisioneDocumento link = linkCondivisione.stream()
.filter(l -> l.getToken().equals(token))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(
"Link non trovato: " + token));
link.revoca();
}
// Query: tutti i link ancora attivi
public List<LinkCondivisioneDocumento> getLinkAttivi() {
return linkCondivisione.stream()
.filter(LinkCondivisioneDocumento::isValido)
.collect(Collectors.toList());
}
자동 메타데이터 추출
파일이 업로드되면,
이벤트를 플레이하세요
다음을 사용하여 사용 가능한 메타데이터를 자동으로 추출합니다. 아파치 티카 그리고
도서관 메타데이터 추출기. 추출된 메타데이터는 JSON으로 저장됩니다.
현장에서 metadati 문서의.
public interface MetadatiDocumentoExtractor {
// Estrae tutti i metadati in base al tipo MIME
Map<String, Object> estraiMetadati(
InputStream inputStream, String nomeFile, String mimeType);
// Specializzazioni per tipo di file
Map<String, Object> estraiMetadatiImmagine(InputStream inputStream);
Map<String, Object> estraiMetadatiPdf(InputStream inputStream);
Map<String, Object> estraiMetadatiOffice(
InputStream inputStream, String mimeType);
boolean isFormatoSupportato(String mimeType);
}
이미지용 메타데이터(EXIF, IPTC, XMP)
이미지의 경우 시스템은 다음 네 가지 기본 디렉터리에서 메타데이터를 추출합니다. EXIF IFD0 (카메라 브랜드, 모델, 소프트웨어, 방향), EXIF 하위IFD (노출 시간, 조리개, ISO, 초점 거리, 플래시), GPS (위도, 경도, 고도) e IPTC (제목, 설명, 키워드, 저자, 도시, 국가).
// MIME types immagine con estrazione EXIF completa
"image/jpeg", "image/png", "image/gif", "image/webp",
"image/tiff", "image/bmp", "image/heic", "image/heif", "image/avif"
// Esempio di metadati estratti da una foto JPEG
{
"exifBase": {
"marca": "Canon",
"modello": "EOS R5",
"software": "Adobe Lightroom 14.0",
"orientamento": "Top, left side (Horizontal)"
},
"exifTecnico": {
"dataScatto": "2026:01:15 14:30:22",
"tempoEsposizione": "1/250 sec",
"apertura": "f/2.8",
"iso": "400",
"lunghezzaFocale": "70 mm",
"flash": "Flash did not fire"
},
"gps": {
"latitudine": 41.1171,
"longitudine": 16.8719
}
}
PDF 및 Office 문서용 메타데이터
PDF 파일의 경우 시스템은 제목, 작성자, 주제, 키워드, 페이지 수, PDF 버전 및 암호화 정보. Office 문서(Word, Excel, 파워포인트, 오픈도큐먼트), 단어수 등의 통계, 문자, 단락, 슬라이드 및 총 편집 시간.
// PDF
"application/pdf"
// Office - Word
"application/msword"
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
// Office - Excel
"application/vnd.ms-excel"
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
// Office - PowerPoint
"application/vnd.ms-powerpoint"
"application/vnd.openxmlformats-officedocument.presentationml.presentation"
// OpenDocument
"application/vnd.oasis.opendocument.text"
"application/vnd.oasis.opendocument.spreadsheet"
"application/vnd.oasis.opendocument.presentation"
// Altri
"text/plain", "text/html", "text/csv",
"application/rtf", "application/zip"
메타데이터는 문서의 JSON 필드에 저장되며 참조할 수 있습니다.
방법을 통해 getMetadato(chiave), aggiungiMetadato(chiave, valore)
e haMetadati() 집계 루트의.
SHA-256을 사용한 파일 무결성
문서의 각 버전에는 해시가 포함됩니다. SHA-256 내용의, 업로드 시 계산됩니다. 이를 통해 언제든지 확인할 수 있습니다 저장 중에 파일이 변경되거나 손상되지 않았는지 확인합니다.
// Calcolo hash al caricamento
public String calcolaHash(InputStream contenuto) {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = contenuto.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
byte[] hash = digest.digest();
return bytesToHex(hash); // 64 caratteri esadecimali
}
// Verifica integrita' su richiesta
public boolean verificaIntegrita(String percorsoRelativo, String hashAtteso) {
if (hashAtteso == null) {
return true; // Se non c'e' hash, considera valido
}
try (InputStream is = leggi(percorsoRelativo).orElse(null)) {
if (is == null) return false;
String hashEffettivo = calcolaHash(is);
return hashAtteso.equals(hashEffettivo);
}
}
파일당 최대 한도는 다음과 같습니다. 50MB, 공장에서 둘 다 검증됨
방법 DocumentoEvento 방법보다 aggiungiVersione().
이 제한을 초과하는 파일은 명시적인 예외와 함께 거부됩니다.
서류 비용 연결
문서를 행사비용과 연결하여 관계를 형성할 수 있습니다. 문서 시스템과 재무 관리 시스템 간의 양방향. 이 영수증, 송장, 영수증을 비용에 첨부하는 데 특히 유용합니다. 특파원.
// Collega il documento a una spesa
public void collegaASpesa(Long spesaId) {
this.spesaId = spesaId;
}
// Rimuove il collegamento con una spesa
public void scollegaDaSpesa() {
this.spesaId = null;
}
// Verifica se il documento e' collegato a una spesa
public boolean isCollegatoASpesa() {
return spesaId != null;
}
// Verifica se il documento e' una ricevuta
public boolean isRicevuta() {
return tipoCartella == TipoCartella.RICEVUTE;
}
인덱스 idx_documento_spesa 칼럼에 spesa_id 최적화하다
특정 비용에 첨부된 모든 문서를 검색하는 쿼리입니다. 이
각 예산 항목과 관련된 영수증을 빠르게 볼 수 있습니다.
이벤트의.
파일 시스템에 구성된 스토리지
스토리지 구현은 패턴을 따릅니다. 육각형 건축:
도메인 레이어는 인터페이스를 정의합니다 DocumentoStorageService, 동안
인프라 계층은 구현을 제공합니다. FileSystemDocumentoStorage.
파일은 명확한 계층 구조로 구성됩니다.
{basePath}/
└── {eventoId}/
├── documenti/
│ └── {documentoId}/
│ ├── v1.pdf
│ ├── v2.pdf
│ └── v3.docx
├── immagini/
│ └── {documentoId}/
│ ├── v1.jpg
│ └── v2.png
└── ricevute/
└── {documentoId}/
├── v1.pdf
└── v2.jpg
// Esempio percorso completo:
// /storage/42/documenti/156/v2.pdf
// /storage/42/immagini/201/v1.jpg
// /storage/42/ricevute/189/v3.pdf
버전 이름 지정은 규칙을 따릅니다. v{numero}.{estensione}, 어디서
확장자는 원래 파일 이름에서 추출됩니다. 스토리지 서비스가 관리합니다.
또한 문서를 삭제한 후 빈 디렉토리를 정리하고 되돌아갑니다.
재귀적으로 트리를 기본 디렉터리로 다시 올라갑니다.
public interface DocumentoStorageService {
// Salvataggio con percorso auto-generato
String salva(Long eventoId, TipoCartella tipoCartella,
Long documentoId, Integer versione,
String nomeFile, InputStream contenuto);
// Lettura file
Optional<InputStream> leggi(String percorsoRelativo);
// Eliminazione singola versione o tutte le versioni
boolean elimina(String percorsoRelativo);
void eliminaTutteVersioni(Long eventoId, TipoCartella tipoCartella,
Long documentoId);
// Integrita' e verifica
String calcolaHash(InputStream contenuto);
boolean verificaIntegrita(String percorsoRelativo, String hashAtteso);
// Monitoraggio spazio
long getSpazioUtilizzato(Long eventoId);
long getDimensione(String percorsoRelativo);
boolean esiste(String percorsoRelativo);
}
방법 getSpazioUtilizzato() 디렉토리를 재귀적으로 탐색합니다.
이벤트의 크기를 계산하고 모든 파일의 크기를 합산하여 공간을 모니터링할 수 있습니다.
각 이벤트마다 디스크를 차지하고 할당량 제한을 구현합니다.
문서 시스템 요약
- 집계 루트:
DocumentoEvento완전한 검증 및 팩토리 메소드를 갖춘 - 11개의 사전 정의된 카테고리 + 아이콘과 색상이 포함된 이벤트별 맞춤 카테고리
- 자동 감지 폴더: MIME 유형 및 카테고리에 따른 /receipts, /images, /documents
- 불변 버전 관리: 변경 및 복원할 때마다 새 버전이 생성됩니다.
- 임시 링크: 만료(최대 30일), 다운로드 제한 및 취소를 통한 공유
- 자동 메타데이터: 이미지, PDF 및 Office 속성용 EXIF/IPTC/XMP
- SHA-256 무결성: 주문형 검증을 통한 콘텐츠 해싱
- 50MB 제한: 생성 및 업데이트 중 차원 검증
- 비용 링크: 예산항목에 첨부할 수 있는 서류
- 계층적 저장소: 이벤트, 폴더 유형 및 문서별 구성
문서 시스템의 소스 코드는 다음에서 확인할 수 있습니다. GitHub. 다음 기사에서는 아키텍처의 또 다른 측면을 살펴보겠습니다. 이벤트를 플레이하세요, 시스템이 서로 다른 제한된 컨텍스트 간의 상호 작용을 관리하는 방법 분석 도메인의.







