package main import ( "context" "fmt" "log" "os" "strings" "time" "git.yuanzoo.cn/zhangzhengqiang/popi-zxl" ) // loadDotEnv 加载当前目录下的 .env,方便本地测试。 func loadDotEnv(path string) { file, err := os.Open(path) if err != nil { return } defer file.Close() content, err := os.ReadFile(path) if err != nil { return } // 按行分割 lines := strings.Split(string(content), "\n") for i := 0; i < len(lines); i++ { line := strings.TrimSpace(lines[i]) if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") { continue } // 检查是否是多行值(以引号开始但不以引号结束) if strings.HasPrefix(line, `ZXCHAIN_PRIVATE_KEY="`) && !strings.HasSuffix(line, `"`) { // 收集多行私钥内容 var privateKeyLines []string privateKeyLines = append(privateKeyLines, strings.TrimPrefix(line, `ZXCHAIN_PRIVATE_KEY="`)) // 继续读取后续行直到找到结束引号 for i+1 < len(lines) && !strings.HasSuffix(strings.TrimSpace(lines[i+1]), `"`) { i++ privateKeyLines = append(privateKeyLines, lines[i]) } // 添加最后一行(去掉结尾引号) if i+1 < len(lines) { i++ lastLine := strings.TrimSpace(lines[i]) if strings.HasSuffix(lastLine, `"`) { privateKeyLines = append(privateKeyLines, strings.TrimSuffix(lastLine, `"`)) } } privateKey := strings.Join(privateKeyLines, "\n") _ = os.Setenv("ZXCHAIN_PRIVATE_KEY", privateKey) continue } // 处理普通单行配置 key, value, ok := strings.Cut(line, "=") if !ok { continue } key = strings.TrimSpace(key) value = strings.TrimSpace(value) if key == "" || os.Getenv(key) != "" { continue } value = strings.Trim(value, `"'`) value = strings.ReplaceAll(value, `\n`, "\n") _ = os.Setenv(key, strings.TrimSpace(value)) } } // newClient 初始化至信链 SDK 客户端。 func newClient() *zxchainsdk.SDKClient { privateKey := os.Getenv("ZXCHAIN_PRIVATE_KEY") client, err := zxchainsdk.NewSDKClient( os.Getenv("ZXCHAIN_SECRET_ID"), os.Getenv("ZXCHAIN_SECRET_KEY"), privateKey, ) if err != nil { log.Fatalf("初始化至信链客户端失败: %v", err) } return client } // h1 演示 Hash 上链。 func h1() { client := newClient() filePath := "/Users/ywlmac/Desktop/81520.jpeg" fileBytes, err := os.ReadFile(filePath) if err != nil { log.Fatalf("读取图片文件失败: %v", err) } result, err := client.CreateHashAttestation(context.Background(), zxchainsdk.HashAttestationRequest{ BizID: fmt.Sprintf("demo-image-%d", time.Now().Unix()), Scene: "sdk-image-hash-demo", ContentBytes: fileBytes, FileName: "81520.jpeg", FileSize: int64(len(fileBytes)), ContentType: "image/jpeg", StorageURI: filePath, Metadata: map[string]string{ "source": "sdk-test", }, }) if err != nil { log.Fatalf("Hash 上链失败: %v", err) } fmt.Println("Hash 上链成功") fmt.Println("evId:", result.ReceiptID) fmt.Println("txId:", result.RequestID) fmt.Println("sm3Hash:", result.SM3Hash) fmt.Println("blockHeight:", result.BlockHeight) fmt.Println("txTime:", result.TxTime) } // h2 演示 Hash 证书查询。 func h2(evID string) { client := newClient() // 把这里替换成 h1 输出的 evId。 // evID := "替换成你的evId" result, err := client.GetHashCert(context.Background(), evID) if err != nil { log.Fatalf("查询 Hash 证书失败: %v", err) } fmt.Println("Hash 证书查询成功") fmt.Println("evId:", result.EvID) fmt.Println("certURL:", result.CertURL) } // h3 演示 Hash 取证查询。 func h3(evID string) { client := newClient() // 至少填写一个查询条件:evId、sm3Hash 或 txId。 req := zxchainsdk.HashAttestationQueryRequest{ EvID: evID, // Hash: "也可以替换成 h1 输出的 sm3Hash", // TxID: "也可以替换成 h1 输出的 txId", } var result *zxchainsdk.HashAttestationQueryResult var err error for attempt := 1; attempt <= 5; attempt++ { result, err = client.QueryHashAttestation(context.Background(), req) if err == nil { break } if attempt < 5 { log.Printf("Hash 取证查询暂未成功,第 %d 次失败:%v,等待后重试", attempt, err) time.Sleep(time.Duration(attempt*2) * time.Second) } } if err != nil { log.Fatalf("Hash 取证查询失败: %v", err) } fmt.Println("Hash 取证查询成功") fmt.Println("evId:", result.EvID) fmt.Println("txId:", result.TxID) fmt.Println("blockHeight:", result.BlockHeight) fmt.Println("txTime:", result.TxTime) fmt.Println("extendInfo:", result.ExtendInfo) } // h4 演示简化的一站式证书生成(从 WorkPreviewURL 自动下载并计算哈希)。 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 // EvidenceHash 不传,SDK 会自动从 WorkPreviewURL 下载并计算 SM3 哈希 CertificateStorage: &zxchainsdk.COSCertificateStorage{ BucketURL: os.Getenv("COS_BUCKET_URL"), PublicBaseURL: os.Getenv("COS_PUBLIC_BASE_URL"), SecretID: os.Getenv("COS_SECRET_ID"), SecretKey: os.Getenv("COS_SECRET_KEY"), }, }) if err != nil { return "", fmt.Errorf("简化证书生成失败: %w", err) } fmt.Println("简化证书生成成功") fmt.Println("UserName:", result.UserName) fmt.Println("UserID:", result.UserID) fmt.Println("WorkName:", result.WorkName) fmt.Println("TaskID:", result.TaskID) fmt.Println("CompletedAt:", result.CompletedAt) fmt.Println("WorkPreviewURL:", result.WorkPreviewURL) fmt.Println("EvidenceHash:", result.EvidenceHash) fmt.Println("ReceiptID (evId):", result.ReceiptID) fmt.Println("RequestID (txId):", result.RequestID) fmt.Println("BlockHeight:", result.BlockHeight) fmt.Println("TxTime:", result.TxTime) fmt.Println("SM3Hash:", result.SM3Hash) fmt.Println("PopiCertificateCertNo:", result.PopiCertificateCertNo) fmt.Println("PopiCertificateVerifyURL:", result.PopiCertificateVerifyURL) fmt.Println("PopiCertificateCertificateImageURL:", result.PopiCertificateCertificateImageURL) fmt.Println("ZXChainCertURL:", result.ZXChainCertURL) return result.ReceiptID, nil } func main() { loadDotEnv(".env") // 只测试 h4 和 h3:先生成证书,再用 h4 返回的 evId 查询 Hash 取证。 evID, err := h4() if err != nil { log.Printf("h4 测试失败: %v", err) evID = os.Getenv("ZXCHAIN_TEST_EV_ID") if evID == "" { evID = "302d355f4b7911f1bf330a77039e1980" } log.Printf("继续使用已有 evId 测试 h3: %s", evID) } h3(evID) // 第一次测试:执行 Hash 上链,复制输出里的 evId。 //h1() // 第二次测试:把 h1() 注释掉,填好 h2 里的 evId,再打开下面这一行。 //h2("302d355f4b7911f1bf330a77039e1980") // 第三次测试:把 h1() 注释掉,填好 h3 里的 evId/sm3Hash/txId,再打开下面这一行。 //h3("302d355f4b7911f1bf330a77039e1980") }