Files
go-trustlog/README.md
ryan d313449c5c refactor: 重构trustlog-sdk目录结构到trustlog/go-trustlog
- 将所有trustlog-sdk文件移动到trustlog/go-trustlog/目录
- 更新README中所有import路径从trustlog-sdk改为go-trustlog
- 更新cookiecutter配置文件中的项目名称
- 更新根目录.lefthook.yml以引用新位置的配置
- 添加go.sum文件到版本控制
- 删除过时的示例文件

这次重构与trustlog-server保持一致的目录结构,
为未来支持多语言SDK(Python、Java等)预留空间。
2025-12-22 13:37:57 +08:00

968 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Trustlog-SDK 使用说明
本 SDK 提供基于 [Watermill](https://watermill.io/) 抽象层的统一消息发送与接收能力,以及基于 gRPC 的操作查询和取证验证功能。
SDK 支持两种数据模型:
- **`Operation`**(操作记录):用于记录完整的业务操作,包含请求/响应体哈希,支持完整的取证验证
- **`Record`**(简单记录):用于记录简单的事件或日志,轻量级,适合日志和事件追踪场景
两种模型分别发布到不同的 Topic通过统一的 `HighClient``QueryClient` 进行操作。支持通过 Watermill Forwarder 将消息持久化到 SQL 数据库,实现事务性保证。
---
## 🚀 安装
### 1. 私有仓库配置(重要)
由于本 SDK 托管在私有仓库,需要配置 SSH 映射和禁用 Go Module 校验:
#### 配置 Git SSH 映射(跳过 HTTPS 验证)
```bash
git config --global url."git@go.yandata.net:".insteadOf "https://go.yandata.net"
```
#### 禁用 Go Module Sum 校验
```bash
go env -w GOPRIVATE="go.yandata.net"
```
### 2. 安装 SDK
```bash
go get go.yandata.net/iod/iod/go-trustlog
```
---
## 📦 核心概念
### 数据模型
SDK 提供两种数据模型,分别适用于不同的业务场景:
#### 1. Operation操作记录
`Operation` 用于记录完整的业务操作,包含完整的元数据、请求/响应体哈希等信息,支持完整的取证验证流程。
**适用场景**
- 记录 DOIP/IRP 协议的完整操作Create、Update、Delete、Retrieve 等)
- 需要完整记录请求和响应的审计场景
- 需要支持完整取证验证的操作记录
**核心字段**
- `Meta`:操作元数据
- `OpID`:操作唯一标识符(自动生成 UUID v7
- `Timestamp`:操作时间戳(必填)
- `OpSource`:操作来源(`DOIP``IRP`
- `OpType`:操作类型(如 `Create``Update``Delete` 等)
- `OpAlgorithm`:哈希算法类型(默认 `Sha256Simd`
- `OpMetaHash`:元数据哈希值(自动计算)
- `DataID`:数据标识
- `DoPrefix`DO 前缀(必填)
- `DoRepository`:仓库名(必填)
- `Doid`:完整 DOID必填格式`{DoPrefix}/{DoRepository}/{object}`
- `OpActor`:操作发起者(默认 `SYSTEM`
- `RequestBodyHash`:请求体哈希值(必填)
- `ResponseBodyHash`:响应体哈希值(必填)
- `OpHash`:操作整体哈希值(自动计算)
**创建方式**
```go
op, err := model.NewFullOperation(
model.OpSourceDOIP, // 操作来源
model.OpTypeCreate, // 操作类型
dataID, // 数据标识
"user123", // 操作者
[]byte(`{"foo":"bar"}`), // 请求体(支持 string 或 []byte
[]byte(`{"status":"ok"}`), // 响应体(支持 string 或 []byte
model.SHA256, // 哈希算法
time.Now(), // 操作时间戳
)
```
**发布方式**
```go
client.OperationPublish(op) // 发布到 OperationTopic
```
#### 2. Record简单记录
`Record` 用于记录简单的事件或日志,轻量级设计,适合日志和事件追踪场景。
**适用场景**
- 记录简单的日志信息
- 记录系统中的事件(如用户登录、配置变更等)
- 不需要完整请求/响应信息的轻量级记录场景
**核心字段**
- `ID`:记录唯一标识符(自动生成 UUID v7
- `DoPrefix`:节点前缀(可选)
- `Timestamp`:操作时间(可选,默认当前时间)
- `Operator`:用户标识(可选)
- `Extra`:额外数据(可选,`[]byte` 类型)
- `RCType`:记录类型(可选,如 `"log"``"event"` 等)
- `Algorithm`:哈希算法类型(默认 `Sha256Simd`
- `RCHash`:记录哈希值(自动计算)
**创建方式**
```go
// 方式一:完整创建
record, err := model.NewFullRecord(
"10.1000", // DoPrefix
time.Now(), // 时间戳
"operator123", // 操作者
[]byte("extra data"), // 额外数据
"log", // 记录类型
model.BLAKE3, // 哈希算法
)
// 方式二:链式调用创建
record, _ := model.NewRecord(model.SHA256)
record.WithDoPrefix("10.1000").
WithTimestamp(time.Now()).
WithOperator("operator123").
WithExtra([]byte("extra data")).
WithRCType("log")
```
**发布方式**
```go
client.RecordPublish(record) // 发布到 RecordTopic
```
#### 两种模型的对比
| 特性 | Operation | Record |
|------|-----------|--------|
| **用途** | 完整业务操作记录 | 简单事件/日志记录 |
| **请求/响应** | ✅ 包含请求体和响应体哈希 | ❌ 不包含 |
| **取证验证** | ✅ 完整取证验证流程 | ✅ 哈希验证 |
| **数据标识** | ✅ 完整的 DataIDPrefix/Repository/Doid | ✅ 可选的 DoPrefix |
| **字段复杂度** | 较高8+ 字段) | 较低7 字段) |
| **Topic** | `persistent://public/default/operation` | `persistent://public/default/record` |
| **适用场景** | 审计、完整操作追踪 | 日志、事件追踪 |
### HashType哈希算法
两种模型都支持以下 18 种哈希算法:
- **MD5 系列**`MD5``MD4`
- **SHA 系列**`SHA1``SHA224``SHA256``SHA384``SHA512``SHA512/224``SHA512/256``SHA256-SIMD`
- **SHA3 系列**`SHA3-224``SHA3-256``SHA3-384``SHA3-512`
- **BLAKE 系列**`BLAKE3``BLAKE2B``BLAKE2S`
- **其他**`RIPEMD160`
默认算法:`Sha256Simd`
### 组件说明
- **Publisher**
负责将 `Operation``Record` 序列化并发布到对应的 Topic
- `Operation``persistent://public/default/operation`
- `Record``persistent://public/default/record`
- **Subscriber**
负责从 Topic 中订阅报文并进行 ack/nack 处理(一般无需直接使用)。可以订阅 `OperationTopic``RecordTopic`
- **HighClient**
高层封装的发布客户端,方便业务代码发送 `Operation``Record` 消息。
- **QueryClient**
基于 gRPC 的统一查询客户端,提供:
- **Operation 操作查询**:列表查询和取证验证
- **Record 记录查询**:列表查询和验证
- **单一连接池**:两种服务共享同一组 gRPC 连接,支持多服务器负载均衡
---
## 🎯 使用场景
### 发布场景
#### Operation 发布场景
- **业务操作记录**:记录 DOIP/IRP 协议的完整操作Create、Update、Delete 等)
- **审计追踪**:需要完整记录请求和响应的审计场景
- **取证验证**:需要支持完整取证验证的操作记录
#### Record 发布场景
- **日志记录**:记录简单的日志信息
- **事件追踪**:记录系统中的事件(如用户登录、配置变更等)
- **轻量级记录**:不需要完整请求/响应信息的场景
**发布方式**
- **直接发布**:使用 Pulsar PublisherSDK 已提供)发送到对应的 Pulsar 主题
- **事务性发布**:使用 Watermill Forwarder 将消息持久化到 SQL 数据库,保证消息的事务性和可靠性
### 查询场景
#### Operation 查询场景
- **操作列表查询**:查询历史操作记录列表(支持分页、按来源/类型/前缀/仓库过滤)
- **取证验证**:对特定操作执行完整的取证验证(流式返回进度)
#### Record 查询场景
- **记录列表查询**:查询历史记录列表(支持分页、按前缀和类型过滤)
- **记录验证**:对特定记录执行哈希验证(流式返回进度)
**统一客户端**`QueryClient` 使用单一连接池同时支持两种服务,共享 gRPC 连接资源
---
## 📝 快速开始
### 1. HighClient 使用(消息发布)
#### 1.1 创建 Logger
SDK 使用 [logr](https://github.com/go-logr/logr) 作为日志接口。你需要先创建一个 logr.Logger 实例,然后通过 `logger.NewLogger()` 包装成 SDK 的 Logger 接口。
##### 方式一:使用默认的 discard logger适用于测试
```go
import (
"go.yandata.net/iod/iod/go-trustlog/api/logger"
"github.com/go-logr/logr"
)
// 使用 discard logger不输出任何日志
myLogger := logger.NewLogger(logr.Discard())
```
##### 方式二:使用 zap推荐生产环境
```go
import (
"go.yandata.net/iod/iod/go-trustlog/api/logger"
"github.com/go-logr/zap"
"go.uber.org/zap"
)
// 创建 zap logger
zapLogger, _ := zap.NewProduction()
// 转换为 logr.Logger
logrLogger := zapr.NewLogger(zapLogger)
// 包装成 SDK 的 Logger
myLogger := logger.NewLogger(logrLogger)
```
##### 方式三:使用其他 logr 实现
```go
import (
"go.yandata.net/iod/iod/go-trustlog/api/logger"
// 可以使用任何实现了 logr.LogSink 的实现
// 例如github.com/go-logr/logr/slogr基于 slog
// github.com/go-logr/zap基于 zap
// github.com/go-logr/logrusr基于 logrus
)
// 假设你有一个 logr.Logger 实例
var logrLogger logr.Logger
myLogger := logger.NewLogger(logrLogger)
```
#### 1.2 创建 Publisher
```go
import (
"go.yandata.net/iod/iod/go-trustlog/api/adapter"
"go.yandata.net/iod/iod/go-trustlog/api/logger"
"github.com/go-logr/logr"
)
// 创建 Logger使用 discard 作为示例)
myLogger := logger.NewLogger(logr.Discard())
// 创建 Pulsar Publisher
pub, err := adapter.NewPublisher(
adapter.PublisherConfig{
URL: "pulsar://localhost:6650",
},
myLogger,
)
if err != nil {
panic(err)
}
defer pub.Close()
```
#### 1.3 使用 HighClient 发送 Operation
```go
import (
"go.yandata.net/iod/iod/go-trustlog/api/highclient"
"go.yandata.net/iod/iod/go-trustlog/api/model"
"time"
)
// 准备SM2密钥十六进制字符串格式
privateKeyHex := []byte("私钥D的十六进制字符串例如abc123...")
publicKeyHex := []byte("04 + x坐标(32字节) + y坐标(32字节)的十六进制字符串")
// 创建Envelope配置
envelopeConfig := model.DefaultEnvelopeConfig(privateKeyHex, publicKeyHex)
// 创建高层客户端使用Envelope序列化方式
client := highclient.NewClient(pub, myLogger, envelopeConfig)
defer client.Close()
// 构造 DataID
dataID := model.DataID{
DoPrefix: "10.1000",
DoRepository: "my-repo",
Doid: "10.1000/my-repo/object123",
}
// 构造完整的 Operation
op, err := model.NewFullOperation(
model.OpSourceDOIP, // 操作来源DOIP 或 IRP
model.OpTypeCreate, // 操作类型Create, Update, Delete 等
dataID, // 数据标识
"user123", // 操作者
[]byte(`{"foo":"bar"}`), // 请求体
[]byte(`{"status":"ok"}`), // 响应体
model.Sha256Simd, // 哈希算法
time.Now(), // 操作时间
)
if err != nil {
panic(err)
}
// 发送 Operation
if err := client.OperationPublish(op); err != nil {
panic(err)
}
```
#### 1.4 使用 HighClient 发送 Record
```go
// 构造 Record
record, err := model.NewFullRecord(
"10.1000", // DoPrefix
time.Now(), // 时间戳
"operator123", // 操作者
[]byte("extra data"), // 额外数据
"log", // 记录类型
model.BLAKE3, // 哈希算法
)
if err != nil {
panic(err)
}
// 发送 Record
if err := client.RecordPublish(record); err != nil {
panic(err)
}
```
#### 1.5 获取底层 Publisher
```go
// 如果需要直接访问 Watermill Publisher
lowPublisher := client.GetLow()
```
---
### 2. QueryClient 使用(统一查询客户端)
`QueryClient` 是统一的查询客户端,同时支持 **Operation操作****Record记录** 两种服务的查询和验证。使用单一连接池,两种服务共享同一组 gRPC 连接。
#### 2.1 创建 QueryClient
##### 单服务器模式
```go
import (
"go.yandata.net/iod/iod/go-trustlog/api/queryclient"
"go.yandata.net/iod/iod/go-trustlog/api/logger"
"github.com/go-logr/logr"
)
// 创建 Logger
myLogger := logger.NewLogger(logr.Discard())
// 创建统一查询客户端(单服务器)
queryClient, err := queryclient.NewClient(
queryclient.ClientConfig{
ServerAddr: "localhost:50051",
},
myLogger,
)
if err != nil {
panic(err)
}
defer queryClient.Close()
```
##### 多服务器负载均衡模式
```go
// 创建查询客户端(多服务器,自动轮询负载均衡)
queryClient, err := queryclient.NewClient(
queryclient.ClientConfig{
ServerAddrs: []string{
"server1:50051",
"server2:50051",
"server3:50051",
},
// DialOptions: []grpc.DialOption{...}, // 可选:自定义 gRPC 连接选项
},
myLogger,
)
if err != nil {
panic(err)
}
defer queryClient.Close()
```
#### 2.2 查询操作列表
```go
import (
"context"
"time"
)
ctx := context.Background()
// 构造查询请求
req := queryclient.ListOperationsRequest{
PageSize: 100, // 每页数量
PreTime: time.Now().Add(-24 * time.Hour), // 游标分页(可选)
// 可选过滤条件
OpSource: model.OpSourceDOIP, // 按操作来源过滤
OpType: model.OpTypeCreate, // 按操作类型过滤
DoPrefix: "10.1000", // 按数据前缀过滤
DoRepository: "my-repo", // 按仓库过滤
}
// 执行查询
resp, err := queryClient.ListOperations(ctx, req)
if err != nil {
panic(err)
}
// 处理结果
fmt.Printf("Total count: %d\n", resp.Count)
for _, op := range resp.Data {
fmt.Printf("Operation ID: %s, Type: %s, Time: %s\n",
op.Meta.OpID, op.Meta.OpType, op.Meta.Timestamp)
}
```
#### 2.3 取证验证(流式)
```go
// 构造验证请求
validationReq := queryclient.ValidationRequest{
Time: time.Now().Add(-1 * time.Hour),
OpID: "operation-id-123",
OpType: "Create",
DoRepository: "my-repo",
}
// 异步验证(流式接收进度)
resultChan, err := queryClient.ValidateOperation(ctx, validationReq)
if err != nil {
panic(err)
}
// 处理流式结果
for result := range resultChan {
if result.IsProcessing() {
fmt.Printf("Progress: %s - %s\n", result.Progress, result.Msg)
} else if result.IsCompleted() {
fmt.Println("Validation completed successfully!")
if result.Data != nil {
fmt.Printf("Operation: %+v\n", result.Data)
}
} else if result.IsFailed() {
fmt.Printf("Validation failed: %s\n", result.Msg)
}
}
```
#### 2.4 取证验证(同步)
```go
// 同步验证(阻塞直到完成)
finalResult, err := queryClient.ValidateOperationSync(
ctx,
validationReq,
func(progress *model.ValidationResult) {
// 可选的进度回调
fmt.Printf("Progress: %s\n", progress.Progress)
},
)
if err != nil {
panic(err)
}
if finalResult.IsCompleted() {
fmt.Println("Validation successful!")
} else {
fmt.Printf("Validation failed: %s\n", finalResult.Msg)
}
```
#### 2.5 查询记录列表Record
```go
// 构造记录查询请求
recordReq := queryclient.ListRecordsRequest{
PageSize: 50, // 每页数量
PreTime: time.Now().Add(-24 * time.Hour), // 游标分页(可选)
// 可选过滤条件
DoPrefix: "10.1000", // 按数据前缀过滤
RCType: "log", // 按记录类型过滤
}
// 执行查询
recordResp, err := queryClient.ListRecords(ctx, recordReq)
if err != nil {
panic(err)
}
// 处理结果
fmt.Printf("Total records: %d\n", recordResp.Count)
for _, rec := range recordResp.Data {
fmt.Printf("Record ID: %s, Type: %s, Hash: %s\n",
rec.ID, rec.RCType, rec.RCHash)
}
```
#### 2.6 记录验证(流式)
```go
// 构造记录验证请求
recordValidationReq := queryclient.RecordValidationRequest{
Timestamp: time.Now().Add(-1 * time.Hour),
RecordID: "record-id-123",
DoPrefix: "10.1000",
RCType: "log",
}
// 异步验证(流式接收进度)
recordResultChan, err := queryClient.ValidateRecord(ctx, recordValidationReq)
if err != nil {
panic(err)
}
// 处理流式结果
for result := range recordResultChan {
if result.IsProcessing() {
fmt.Printf("Progress: %s - %s\n", result.Progress, result.Msg)
} else if result.IsCompleted() {
fmt.Println("Record validation completed!")
if result.Data != nil {
fmt.Printf("Record: %+v\n", result.Data)
}
} else if result.IsFailed() {
fmt.Printf("Record validation failed: %s\n", result.Msg)
}
}
```
#### 2.7 记录验证(同步)
```go
// 同步验证(阻塞直到完成)
finalRecordResult, err := queryClient.ValidateRecordSync(
ctx,
recordValidationReq,
func(progress *model.RecordValidationResult) {
// 可选的进度回调
fmt.Printf("Progress: %s\n", progress.Progress)
},
)
if err != nil {
panic(err)
}
if finalRecordResult.IsCompleted() {
fmt.Println("Record validation successful!")
} else {
fmt.Printf("Record validation failed: %s\n", finalRecordResult.Msg)
}
```
#### 2.8 获取底层 gRPC 客户端
```go
// 高级用户可以直接访问 gRPC 客户端进行自定义操作
// 获取 Operation 服务客户端
opGrpcClient := queryClient.GetLowLevelOperationClient()
// 获取 Record 服务客户端
recGrpcClient := queryClient.GetLowLevelRecordClient()
// 注意:多服务器模式下,每次调用会返回轮询的下一个客户端
```
---
### 3. Subscriber 使用(消息订阅)
> **注意**:通常业务代码不需要直接使用 Subscriber除非需要原始的 Watermill 消息处理。
```go
import (
"context"
"go.yandata.net/iod/iod/go-trustlog/api/adapter"
"go.yandata.net/iod/iod/go-trustlog/api/model"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/bytedance/sonic"
"github.com/apache/pulsar-client-go/pulsar"
"github.com/go-logr/logr"
"go.yandata.net/iod/iod/go-trustlog/api/logger"
)
// 创建 Logger
myLogger := logger.NewLogger(logr.Discard())
// 创建订阅者
sub, err := adapter.NewSubscriber(
adapter.SubscriberConfig{
URL: "pulsar://localhost:6650",
SubscriberType: pulsar.KeyShared, // 必须使用 KeyShared 模式
},
myLogger,
)
if err != nil {
panic(err)
}
defer sub.Close()
// 订阅消息context 必须携带 key 为 "subName" 的 value
ctx := context.WithValue(context.Background(), "subName", "my-subscriber")
msgChan, err := sub.Subscribe(ctx, adapter.OperationTopic) // 或者 adapter.RecordTopic
if err != nil {
panic(err)
}
// 处理消息
for msg := range msgChan {
var op model.Operation
if err := sonic.Unmarshal(msg.Payload, &op); err != nil {
myLogger.ErrorContext(ctx, "Invalid Operation message", "error", err)
msg.Nack()
continue
}
// 处理业务逻辑
myLogger.InfoContext(ctx, "Received Operation", "key", op.Key())
// 根据业务成功与否 ack / nack
msg.Ack()
}
```
---
### 4. Forwarder 事务性发布SQL持久化
使用 Watermill Forwarder 可以将消息先持久化到 SQL 数据库,然后异步发送到 Pulsar保证消息的事务性和可靠性。
这在需要确保消息不丢失的场景下非常有用。
```go
import (
"database/sql"
"github.com/ThreeDotsLabs/watermill/components/forwarder"
"github.com/ThreeDotsLabs/watermill-sql/v3/pkg/sql"
"github.com/go-logr/logr"
"go.yandata.net/iod/iod/go-trustlog/api/adapter"
"go.yandata.net/iod/iod/go-trustlog/api/highclient"
"go.yandata.net/iod/iod/go-trustlog/api/logger"
)
// 0. 创建 Logger
myLogger := logger.NewLogger(logr.Discard())
// 1. 创建 SQL Publisher用于持久化
db, err := sql.Open("postgres", "postgres://user:pass@localhost/db")
if err != nil {
panic(err)
}
sqlPublisher, err := watermillsql.NewPublisher(
db,
watermillsql.PublisherConfig{
SchemaAdapter: watermillsql.DefaultPostgreSQLSchema{},
},
myLogger,
)
if err != nil {
panic(err)
}
// 2. 创建 Pulsar Publisher实际发送
pulsarPublisher, err := adapter.NewPublisher(
adapter.PublisherConfig{URL: "pulsar://localhost:6650"},
myLogger,
)
if err != nil {
panic(err)
}
// 3. 创建 ForwarderSQL -> Pulsar
// 消息先写入 SQL事务提交后异步转发到 Pulsar
fwd, err := forwarder.NewForwarder(sqlPublisher, pulsarPublisher)
if err != nil {
panic(err)
}
// 4. 使用 Forwarder 创建客户端
// 发布的消息会先存储到 SQL保证事务性
client := highclient.NewClient(fwd, myLogger)
defer client.Close()
// 5. 在数据库事务中发布消息
tx, _ := db.Begin()
// ... 执行业务数据库操作 ...
// 发布 Operation会在同一个事务中写入
_ = client.OperationPublish(op)
// 提交事务(业务数据和消息同时提交)
tx.Commit()
```
> **优势**
> - ✅ 消息与业务数据在同一事务中,保证强一致性
> - ✅ 即使 Pulsar 暂时不可用,消息也不会丢失
> - ✅ Forwarder 会自动重试发送失败的消息
---
## 🎨 完整示例
### 发布 + 查询 + 验证完整流程
```go
package main
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
"go.yandata.net/iod/iod/go-trustlog/api/adapter"
"go.yandata.net/iod/iod/go-trustlog/api/highclient"
"go.yandata.net/iod/iod/go-trustlog/api/logger"
"go.yandata.net/iod/iod/go-trustlog/api/queryclient"
"go.yandata.net/iod/iod/go-trustlog/api/model"
)
func main() {
ctx := context.Background()
// 0. 创建 Logger
myLogger := logger.NewLogger(logr.Discard())
// 1. 创建并发送 Operation
pub, _ := adapter.NewPublisher(
adapter.PublisherConfig{URL: "pulsar://localhost:6650"},
myLogger,
)
defer pub.Close()
// 准备SM2密钥
privateKeyHex := []byte("私钥D的十六进制字符串")
publicKeyHex := []byte("04 + x坐标 + y坐标的十六进制字符串")
envelopeConfig := model.DefaultEnvelopeConfig(privateKeyHex, publicKeyHex)
client := highclient.NewClient(pub, myLogger, envelopeConfig)
defer client.Close()
dataID := model.DataID{
DoPrefix: "10.1000",
DoRepository: "test-repo",
Doid: "10.1000/test-repo/doc001",
}
op, _ := model.NewFullOperation(
model.OpSourceDOIP,
model.OpTypeCreate,
dataID,
"admin",
[]byte(`{"action":"create"}`),
[]byte(`{"status":"success"}`),
model.SHA256,
time.Now(),
)
_ = client.OperationPublish(op)
fmt.Printf("Published operation: %s\n", op.Meta.OpID)
// 等待一段时间让消息被处理
time.Sleep(2 * time.Second)
// 2. 查询操作列表
queryClient, _ := queryclient.NewClient(
queryclient.ClientConfig{ServerAddr: "localhost:50051"},
myLogger,
)
defer queryClient.Close()
listResp, _ := queryClient.ListOperations(ctx, queryclient.ListOperationsRequest{
PageSize: 10,
DoRepository: "test-repo",
})
fmt.Printf("Found %d operations\n", listResp.Count)
// 3. 执行取证验证
if len(listResp.Data) > 0 {
firstOp := listResp.Data[0]
validationReq := queryclient.ValidationRequest{
Time: firstOp.Meta.Timestamp,
OpID: firstOp.Meta.OpID,
OpType: string(firstOp.Meta.OpType),
DoRepository: firstOp.DataID.DoRepository,
}
result, _ := queryClient.ValidateOperationSync(ctx, validationReq, nil)
if result.IsCompleted() {
fmt.Println("✅ Validation passed!")
} else {
fmt.Printf("❌ Validation failed: %s\n", result.Msg)
}
}
}
```
---
## 📚 操作类型枚举
### DOIP 操作类型7种
```go
model.OpTypeHello // Hello 握手
model.OpTypeRetrieve // 检索资源
model.OpTypeCreate // 新建资源
model.OpTypeDelete // 删除资源
model.OpTypeUpdate // 更新资源
model.OpTypeSearch // 搜索资源
model.OpTypeListOperations // 列出可用操作
```
### IRP 操作类型33种
```go
// Handle 基础操作
model.OpTypeOCReserved, model.OpTypeOCResolution, model.OpTypeOCGetSiteInfo
model.OpTypeOCCreateHandle, model.OpTypeOCDeleteHandle, model.OpTypeOCAddValue
model.OpTypeOCRemoveValue, model.OpTypeOCModifyValue, model.OpTypeOCListHandle
model.OpTypeOCListNA
// DOID 操作
model.OpTypeOCResolutionDOID, model.OpTypeOCCreateDOID, model.OpTypeOCDeleteDOID
model.OpTypeOCUpdateDOID, model.OpTypeOCBatchCreateDOID, model.OpTypeOCResolutionDOIDRecursive
// 用户与仓库
model.OpTypeOCGetUsers, model.OpTypeOCGetRepos
// GRS/IRS 管理
model.OpTypeOCVerifyIRS, model.OpTypeOCResolveGRS, model.OpTypeOCCreateOrgGRS
model.OpTypeOCUpdateOrgGRS, model.OpTypeOCDeleteOrgGRS, model.OpTypeOCSyncOrgIRSParent
model.OpTypeOCUpdateOrgIRSParent, model.OpTypeOCDeleteOrgIRSParent
// 安全与会话
model.OpTypeOCChallengeResponse, model.OpTypeOCVerifyChallenge, model.OpTypeOCSessionSetup
model.OpTypeOCSessionTerminate, model.OpTypeOCSessionExchangeKey, model.OpTypeOCVerifyRouter
model.OpTypeOCQueryRouter
```
---
## ⚠️ 注意事项
1. **私有仓库配置**
必须先配置 Git SSH 映射和 GOPRIVATE 环境变量,否则无法正常安装 SDK。
2. **日志接口**
SDK 使用 [logr](https://github.com/go-logr/logr) 作为日志接口。你需要:
- 创建一个 `logr.Logger` 实例(可以使用 zap、logrus 等实现)
- 通过 `logger.NewLogger(logrLogger)` 包装成 SDK 的 Logger 接口
- 在生产环境建议使用 `zapr``logrusr` 等实现,测试环境可以使用 `logr.Discard()`
3. **HighClient 方法名**
- 发送 Operation 使用 `client.OperationPublish(op)`,参数为指针类型 `*model.Operation`
- 发送 Record 使用 `client.RecordPublish(record)`,参数为指针类型 `*model.Record`
4. **固定主题**
- Operation 主题:`persistent://public/default/operation`
- Record 主题:`persistent://public/default/record`
5. **KeyShared 消费模式**
由于 Trustlog 使用 Key Shared 消费模式,其他订阅者必须选择 KeyShared 并避免消费者重名。
6. **ack/nack 必须处理**
确保订阅方根据业务逻辑确认或拒绝消息。
7. **时间戳处理**
`NewFullOperation()` 接受 `time.Time` 类型的时间戳参数。
8. **统一连接池**
QueryClient 使用单一连接池同时支持 Operation 和 Record 两种服务,共享 gRPC 连接资源,提高资源利用率。
9. **负载均衡**
支持多服务器轮询负载均衡,自动分发请求到不同服务器,连接在两种服务间共享。
10. **流式验证**
取证验证Operation 和 Record都支持流式和同步两种模式流式模式可实时获取进度。
11. **事务性发布**
使用 Watermill Forwarder 可以将消息持久化到 SQL与业务数据在同一事务中提交保证强一致性。
12. **Record 支持**
除了 OperationSDK 现在也支持 Record 类型的发布、查询和验证,两种服务使用同一个 QueryClient。
---
## 🔄 架构图
### 直接发布架构
```
[业务服务]
[HighClient.Publish()]
[Pulsar Publisher] --(Operation JSON)--> [Pulsar Topic]
[Subscriber]
[其他服务]
```
### 事务性发布架构(使用 Forwarder
```
[业务服务 + DB事务]
[HighClient.Publish()]
[SQL Publisher] --写入--> [PostgreSQL/MySQL]
↓ ↓
[Forwarder 后台轮询] |
↓ |
[读取未发送消息] <--------------┘
[Pulsar Publisher] --(Operation JSON)--> [Pulsar Topic]
↓ ↓
[标记为已发送] [Subscriber]
[其他服务]
```
### 查询架构(统一连接池)
```
[业务服务]
[QueryClient - 单一连接池]
├─ Operation 服务客户端 ─┐
└─ Record 服务客户端 ────┤
↓ (共享 gRPC 连接,轮询负载均衡)
[Server 1] ─┐
[Server 2] ─┼─ 多服务器
[Server 3] ─┘
[存储层]
优势:
- 单一连接池,资源高效利用
- Operation 和 Record 服务共享连接
- 自动负载均衡,请求分发到不同服务器
- 减少连接数,降低服务器压力
```
---