From 9f5a42e0bb997eb66ffddaac65a88d73f6266ae6 Mon Sep 17 00:00:00 2001 From: ywlmac Date: Wed, 13 May 2026 12:51:14 +0800 Subject: [PATCH] Refactor project structure --- error.go | 15 ---- examples/manual/main.go => main.go | 29 +++++-- .../zxchainsdk/certificate.go | 79 ++++++++++++++++--- .../zxchainsdk/certificate_flow.go | 2 +- client.go => pkg/zxchainsdk/client.go | 0 model.go => pkg/zxchainsdk/model.go | 0 sdk_client.go => pkg/zxchainsdk/sdk_client.go | 52 ++++++------ 7 files changed, 115 insertions(+), 62 deletions(-) delete mode 100644 error.go rename examples/manual/main.go => main.go (85%) rename certificate.go => pkg/zxchainsdk/certificate.go (85%) rename certificate_flow.go => pkg/zxchainsdk/certificate_flow.go (99%) rename client.go => pkg/zxchainsdk/client.go (100%) rename model.go => pkg/zxchainsdk/model.go (100%) rename sdk_client.go => pkg/zxchainsdk/sdk_client.go (54%) diff --git a/error.go b/error.go deleted file mode 100644 index 2a449b0..0000000 --- a/error.go +++ /dev/null @@ -1,15 +0,0 @@ -package zxchainsdk - -import "errors" - -var ( - ErrMissingBizID = errors.New("missing biz_id") - ErrMissingContent = errors.New("missing content") - ErrMissingEvID = errors.New("missing ev_id") - ErrMissingHash = errors.New("missing hash") - ErrMissingScene = errors.New("missing scene") - ErrMissingKVFields = errors.New("missing kv fields") - ErrMissingFileName = errors.New("missing file_name") - ErrMissingQueryKey = errors.New("missing ev_id, hash or tx_id") - ErrEmptyAttestation = errors.New("empty attestation request") -) diff --git a/examples/manual/main.go b/main.go similarity index 85% rename from examples/manual/main.go rename to main.go index 03b4c1f..6b0f0a9 100644 --- a/examples/manual/main.go +++ b/main.go @@ -8,9 +8,21 @@ import ( "strings" "time" - "git.yuanzoo.cn/zhangzhengqiang/popi-zxl" + "git.yuanzoo.cn/zhangzhengqiang/popi-zxl/pkg/zxchainsdk" ) +const ( + defaultDemoWorkPreviewURL = "https://popiart-public-1313913486.cos.ap-guangzhou.myqcloud.com/2026-05-09-6.jpg" + defaultTestEvID = "302d355f4b7911f1bf330a77039e1980" +) + +func getenvDefault(key, fallback string) string { + if value := strings.TrimSpace(os.Getenv(key)); value != "" { + return value + } + return fallback +} + // loadDotEnv 加载当前目录下的 .env,方便本地测试。 func loadDotEnv(path string) { file, err := os.Open(path) @@ -184,12 +196,13 @@ func h4() (string, error) { client := newClient() result, err := client.CreateSimpleHashAttestationCertificate(context.Background(), zxchainsdk.SimpleHashAttestationCertificateRequest{ - UserName: "zzzzzq", // 申请用户名称 - UserID: "56142321", // 申请用户 ID - WorkName: "测试作品", // 作品名称 - TaskID: "1231223", // 生成任务 ID - CompletedAt: "2026.5.11 15:20:01", // 作品完成时间 - WorkPreviewURL: "https://popiart-public-1313913486.cos.ap-guangzhou.myqcloud.com/media/2026/0511/83218.jpg", // 作品预览图 URL + UserName: getenvDefault("ZXCHAIN_TEST_USER_NAME", "zzzzzq"), // 申请用户名称 + UserID: getenvDefault("ZXCHAIN_TEST_USER_ID", "56142321"), // 申请用户 ID + WorkName: getenvDefault("ZXCHAIN_TEST_WORK_NAME", "测试作品"), // 作品名称 + TaskID: getenvDefault("ZXCHAIN_TEST_TASK_ID", "1231223"), // 生成任务 ID + CompletedAt: getenvDefault("ZXCHAIN_TEST_COMPLETED_AT", "2026.5.11 15:20:01"), // 作品完成时间 + WorkPreviewURL: getenvDefault("ZXCHAIN_WORK_PREVIEW_URL", defaultDemoWorkPreviewURL), // 作品预览图 URL + EvidenceHash: os.Getenv("ZXCHAIN_EVIDENCE_HASH"), // 可选:传入后不下载预览图计算哈希 // EvidenceHash 不传,SDK 会自动从 WorkPreviewURL 下载并计算 SM3 哈希 CertificateStorage: &zxchainsdk.COSCertificateStorage{ @@ -233,7 +246,7 @@ func main() { log.Printf("h4 测试失败: %v", err) evID = os.Getenv("ZXCHAIN_TEST_EV_ID") if evID == "" { - evID = "302d355f4b7911f1bf330a77039e1980" + evID = defaultTestEvID } log.Printf("继续使用已有 evId 测试 h3: %s", evID) } diff --git a/certificate.go b/pkg/zxchainsdk/certificate.go similarity index 85% rename from certificate.go rename to pkg/zxchainsdk/certificate.go index aa39b89..ae65639 100644 --- a/certificate.go +++ b/pkg/zxchainsdk/certificate.go @@ -23,8 +23,8 @@ import ( ) const ( - defaultCertificateBackgroundURL = "https://popiart-public-1313913486.cos.ap-guangzhou.myqcloud.com/media/2026/0511/83034.png" - defaultCertificateLogoURL = "https://popiart-public-1313913486.cos.ap-guangzhou.myqcloud.com/media/2026/0511/83035.png" + defaultCertificateBackgroundURL = "https://popiart-public-1313913486.cos.ap-guangzhou.myqcloud.com/media/2026/0513/84426.png" + defaultCertificateLogoURL = "https://popiart-public-1313913486.cos.ap-guangzhou.myqcloud.com/media/2026/0513/84428.png" defaultCertificateBucketBaseURL = "https://popi-certificate-1386008232.cos.ap-guangzhou.myqcloud.com" ) @@ -83,23 +83,33 @@ func GenerateHashCertificate(ctx context.Context, req HashCertificateRequest) (* if req.Title == "" { req.Title = "数字作品区块链存证证明" } - if req.BackgroundURL == "" && len(req.BackgroundBytes) == 0 { - req.BackgroundURL = defaultCertificateBackgroundURL - } - if req.LogoURL == "" && len(req.LogoBytes) == 0 { - req.LogoURL = defaultCertificateLogoURL - } if req.BucketBaseURL == "" { req.BucketBaseURL = defaultCertificateBucketBaseURL } - background, err := loadCertificateImage(ctx, req.HTTPClient, req.BackgroundURL, req.BackgroundBytes) + var err error + backgroundURL := req.BackgroundURL + if backgroundURL == "" && len(req.BackgroundBytes) == 0 { + backgroundURL = defaultCertificateBackgroundURL + } + background, err := loadCertificateImage(ctx, req.HTTPClient, backgroundURL, req.BackgroundBytes) if err != nil { - return nil, fmt.Errorf("load certificate background: %w", err) + if req.BackgroundURL != "" || len(req.BackgroundBytes) > 0 { + return nil, fmt.Errorf("load certificate background: %w", err) + } + background = defaultCertificateBackgroundImage() + } + + logoURL := req.LogoURL + if logoURL == "" && len(req.LogoBytes) == 0 { + logoURL = defaultCertificateLogoURL } - logo, err := loadCertificateImage(ctx, req.HTTPClient, req.LogoURL, req.LogoBytes) + logo, err := loadCertificateImage(ctx, req.HTTPClient, logoURL, req.LogoBytes) if err != nil { - return nil, fmt.Errorf("load certificate logo: %w", err) + if req.LogoURL != "" || len(req.LogoBytes) > 0 { + return nil, fmt.Errorf("load certificate logo: %w", err) + } + logo = defaultCertificateLogoImage() } var workPreview image.Image @@ -266,6 +276,51 @@ func newQRCodeImage(content string, size int) (image.Image, error) { return qr.Image(size), nil } +func defaultCertificateBackgroundImage() image.Image { + dc := gg.NewContext(1369, 1920) + dc.SetHexColor("#fffdf8") + dc.Clear() + + gradient := gg.NewLinearGradient(0, 0, 1369, 1920) + gradient.AddColorStop(0, mustHexColor("#ffffff")) + gradient.AddColorStop(0.55, mustHexColor("#fff7e7")) + gradient.AddColorStop(1, mustHexColor("#f4e4c5")) + dc.SetFillStyle(gradient) + dc.DrawRectangle(0, 0, 1369, 1920) + dc.Fill() + + dc.SetHexColor("#d7b56d") + dc.SetLineWidth(4) + dc.DrawRectangle(60, 60, 1249, 1800) + dc.Stroke() + + dc.SetHexColor("#eed594") + dc.SetLineWidth(1) + dc.DrawRectangle(82, 82, 1205, 1756) + dc.Stroke() + + return dc.Image() +} + +func defaultCertificateLogoImage() image.Image { + dc := gg.NewContext(304, 60) + dc.SetRGBA(0, 0, 0, 0) + dc.Clear() + dc.SetHexColor("#111111") + dc.DrawCircle(32, 30, 24) + dc.Fill() + dc.SetHexColor("#eed594") + dc.DrawCircle(32, 30, 13) + dc.Fill() + dc.SetHexColor("#111111") + dc.DrawCircle(32, 30, 6) + dc.Fill() + dc.SetHexColor("#111111") + dc.DrawRoundedRectangle(74, 16, 170, 28, 14) + dc.Fill() + return dc.Image() +} + func loadCertificateImage(ctx context.Context, client *http.Client, imageURL string, inline []byte) (image.Image, error) { var data []byte var err error diff --git a/certificate_flow.go b/pkg/zxchainsdk/certificate_flow.go similarity index 99% rename from certificate_flow.go rename to pkg/zxchainsdk/certificate_flow.go index 1ab2674..6c6c57c 100644 --- a/certificate_flow.go +++ b/pkg/zxchainsdk/certificate_flow.go @@ -372,7 +372,7 @@ func UploadCertificatePNGToCOS(ctx context.Context, pngBytes []byte, defaultObje _, err = client.Object.Put(ctx, objectKey, bytes.NewReader(pngBytes), &cos.ObjectPutOptions{ ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{ ContentType: "image/png", - ContentLength: int64(len(pngBytes)), + ContentLength: len(pngBytes), CacheControl: "public, max-age=31536000, immutable", }, }) diff --git a/client.go b/pkg/zxchainsdk/client.go similarity index 100% rename from client.go rename to pkg/zxchainsdk/client.go diff --git a/model.go b/pkg/zxchainsdk/model.go similarity index 100% rename from model.go rename to pkg/zxchainsdk/model.go diff --git a/sdk_client.go b/pkg/zxchainsdk/sdk_client.go similarity index 54% rename from sdk_client.go rename to pkg/zxchainsdk/sdk_client.go index 1436133..40db0cb 100644 --- a/sdk_client.go +++ b/pkg/zxchainsdk/sdk_client.go @@ -50,19 +50,19 @@ func (c *SDKClient) CreateHashAttestation(_ context.Context, req HashAttestation raw, _ := json.Marshal(resp) return &AttestationResult{ - ReceiptID: resp.EvId, // ReceiptID 保存官方 EvSave 返回的 evId。 - RequestID: resp.TxId, // RequestID 保存官方 EvSave 返回的 txId。 - Status: "success", // Status 表示本次 SDK 调用成功。 - RawResponse: raw, // RawResponse 保存官方 SDK 原始响应 JSON。 - ExternalRefID: req.BizID, // ExternalRefID 关联业务侧传入的 BizID。 - BlockHeight: resp.BlockHeight, // BlockHeight 表示上链区块高度。 - TxTime: resp.TxTime, // TxTime 表示官方返回的上链时间。 - FileName: req.FileName, // FileName 回传业务侧传入的文件名。 - FileSize: int64(len(data)), // FileSize 表示本次参与 Hash 存证的内容字节数。 - ContentType: req.ContentType, // ContentType 回传业务侧传入的内容类型。 - StorageURI: req.StorageURI, // StorageURI 回传业务侧保存原始文件的位置。 - SM3Hash: sm3Hash, // SM3Hash 表示本次上链内容的 SM3 哈希。 - CreatedAt: createdAt, // CreatedAt 表示业务侧传入或 SDK 自动生成的创建时间。 + ReceiptID: resp.EvId, + RequestID: resp.TxId, + Status: "success", + RawResponse: raw, + ExternalRefID: req.BizID, + BlockHeight: resp.BlockHeight, + TxTime: resp.TxTime, + FileName: req.FileName, + FileSize: int64(len(data)), + ContentType: req.ContentType, + StorageURI: req.StorageURI, + SM3Hash: sm3Hash, + CreatedAt: createdAt, }, nil } @@ -79,12 +79,12 @@ func (c *SDKClient) QueryHashAttestation(_ context.Context, req HashAttestationQ raw, _ := json.Marshal(resp) return &HashAttestationQueryResult{ - EvID: resp.EvId, // EvID 表示查询到的 Hash 存证 evId。 - TxID: resp.TxId, // TxID 表示查询到的链上交易 txId。 - BlockHeight: resp.BlockHeight, // BlockHeight 表示查询到的上链区块高度。 - TxTime: resp.TxTime, // TxTime 表示查询到的上链时间。 - ExtendInfo: resp.ExtendInfo, // ExtendInfo 表示存证时传入的业务场景信息。 - RawResponse: raw, // RawResponse 保存官方 SDK 原始响应 JSON。 + EvID: resp.EvId, + TxID: resp.TxId, + BlockHeight: resp.BlockHeight, + TxTime: resp.TxTime, + ExtendInfo: resp.ExtendInfo, + RawResponse: raw, }, nil } @@ -103,11 +103,11 @@ func (c *SDKClient) CreateKVAttestation(_ context.Context, req KVAttestationRequ raw, _ := json.Marshal(resp) return &AttestationResult{ - ReceiptID: resp.TxId, // ReceiptID 保存 KV 查询结果中的 txId。 - RequestID: resp.TxId, // RequestID 保存 KV 查询结果中的 txId。 - Status: "success", // Status 表示本次 SDK 调用成功。 - RawResponse: raw, // RawResponse 保存官方 SDK 原始响应 JSON。 - ExternalRefID: resp.KvKey, // ExternalRefID 保存链上的 kvKey。 + ReceiptID: resp.TxId, + RequestID: resp.TxId, + Status: "success", + RawResponse: raw, + ExternalRefID: resp.KvKey, }, nil } @@ -123,7 +123,7 @@ func (c *SDKClient) GetHashCert(_ context.Context, evID string) (*HashCertResult } return &HashCertResult{ - EvID: evID, // EvID 表示 Hash 存证回执中的 evId。 - CertURL: certURL, // CertURL 表示官方证书访问地址。 + EvID: evID, + CertURL: certURL, }, nil }