diff --git a/backend/internal/certificate/lifecycle.go b/backend/internal/certificate/lifecycle.go index 0bccf82..7dc2ad0 100644 --- a/backend/internal/certificate/lifecycle.go +++ b/backend/internal/certificate/lifecycle.go @@ -44,6 +44,7 @@ type SavedResource struct { CertificateType string `json:"certificate_type,omitempty"` CertificateIdentifier string `json:"certificate_identifier,omitempty"` Version string `json:"version,omitempty"` + SavedAt time.Time `json:"saved_at"` SANs []string `json:"sans,omitempty"` SerialNumber string `json:"serial_number,omitempty"` FingerprintSHA256 string `json:"fingerprint_sha256,omitempty"` @@ -185,6 +186,10 @@ func (s FileStore) save(ctx context.Context, domain, certificateType string, res return SavedResource{}, errors.New("证书存储根目录不能为空") } baseDirectory := filepath.Join(root, directoryName(primary)) + savedAt := time.Now().UTC() + if s.Now != nil { + savedAt = s.Now().UTC() + } typeDirectory := baseDirectory directory := baseDirectory certificateIdentifier := "" @@ -211,11 +216,7 @@ func (s FileStore) save(ctx context.Context, domain, certificateType string, res if !directoryContainsCertificateFiles(activeDirectory) { activeDirectory = "" } - now := time.Now().UTC() - if s.Now != nil { - now = s.Now().UTC() - } - version, err = nextCertificateVersion(directory, now) + version, err = nextCertificateVersion(directory, savedAt) if err != nil { return SavedResource{}, err } @@ -244,7 +245,7 @@ func (s FileStore) save(ctx context.Context, domain, certificateType string, res leaf, _ := parseLeafCertificate(resource.Certificate) result := SavedResource{ Domain: primary, CertificateType: certificateType, CertificateIdentifier: certificateIdentifier, - Version: version, SANs: append([]string(nil), resource.Domains...), + Version: version, SavedAt: savedAt, SANs: append([]string(nil), resource.Domains...), Directory: currentDirectory, CertificatePath: filepath.Join(currentDirectory, "cert.pem"), FullChainPath: filepath.Join(currentDirectory, "fullchain.pem"), PrivateKeyPath: filepath.Join(currentDirectory, "key.pem"), NotBefore: resource.NotBefore, NotAfter: resource.NotAfter, BackupDirectory: backupDirectory, diff --git a/backend/internal/certificate/lifecycle_version_test.go b/backend/internal/certificate/lifecycle_version_test.go index c4f6379..af34f15 100644 --- a/backend/internal/certificate/lifecycle_version_test.go +++ b/backend/internal/certificate/lifecycle_version_test.go @@ -29,6 +29,10 @@ func TestFileStoreUsesSingleDatedCurrentVersion(t *testing.T) { if first.Version != "20260827-V1.0" || second.Version != "20260827-V1.1" { t.Fatalf("证书版本命名错误:first=%q second=%q", first.Version, second.Version) } + expectedSavedAt := time.Date(2026, 8, 27, 12, 0, 0, 0, time.UTC) + if !first.SavedAt.Equal(expectedSavedAt) || !second.SavedAt.Equal(expectedSavedAt) { + t.Fatalf("saved timestamp mismatch: first=%s second=%s", first.SavedAt, second.SavedAt) + } wantCurrent := filepath.Join(root, "ssl", "example.com", "single", second.Version) if second.Directory != wantCurrent { t.Fatalf("当前目录错误:got=%q want=%q", second.Directory, wantCurrent) @@ -68,7 +72,7 @@ func TestFileStoreUsesSingleDatedCurrentVersion(t *testing.T) { if err != nil { t.Fatalf("读取当前证书失败:%v", err) } - if metadata.Version != second.Version || metadata.Directory != second.Directory || len(loaded.Certificate) == 0 || len(loaded.PrivateKey) == 0 { + if metadata.Version != second.Version || metadata.Directory != second.Directory || !metadata.SavedAt.Equal(expectedSavedAt) || len(loaded.Certificate) == 0 || len(loaded.PrivateKey) == 0 { t.Fatalf("当前证书内容错误:metadata=%+v", metadata) } }