docs: 修复 README 示例与当前 API 的一致性 - OpType改为string,NewFullOperation参数修正,查询API统一,Persistence完整示例,明确所有存证策略都支持SM2签名

This commit is contained in:
ryan
2025-12-25 10:55:16 +08:00
parent e54d3c96b7
commit a61b1a8840

159
README.md
View File

@@ -89,7 +89,7 @@ SDK 提供两种数据模型,分别适用于不同的业务场景:
- `OpID`:操作唯一标识符(自动生成 UUID v7 - `OpID`:操作唯一标识符(自动生成 UUID v7
- `Timestamp`:操作时间戳(必填) - `Timestamp`:操作时间戳(必填)
- `OpSource`:操作来源(`DOIP``IRP` - `OpSource`:操作来源(`DOIP``IRP`
- `OpType`:操作类型(如 `Create``Update``Delete` 等) - `OpType`:操作类型(如 `"Create"``"Update"``"Delete"`,字符串类型
- `OpAlgorithm`:哈希算法类型(默认 `Sha256Simd` - `OpAlgorithm`:哈希算法类型(默认 `Sha256Simd`
- `OpMetaHash`:元数据哈希值(自动计算) - `OpMetaHash`:元数据哈希值(自动计算)
- `DataID`:数据标识 - `DataID`:数据标识
@@ -105,12 +105,14 @@ SDK 提供两种数据模型,分别适用于不同的业务场景:
```go ```go
op, err := model.NewFullOperation( op, err := model.NewFullOperation(
model.OpSourceDOIP, // 操作来源 model.OpSourceDOIP, // 操作来源
model.OpTypeCreate, // 操作类型 string(model.OpTypeCreate), // 操作类型(字符串)
dataID, // 数据标识 "10.1000", // doPrefix
"user123", // 操作者 "my-repo", // doRepository
"10.1000/my-repo/object123", // doid完整标识
"producer-123", // producerID
"user123", // opActor
[]byte(`{"foo":"bar"}`), // 请求体(支持 string 或 []byte []byte(`{"foo":"bar"}`), // 请求体(支持 string 或 []byte
[]byte(`{"status":"ok"}`), // 响应体(支持 string 或 []byte []byte(`{"status":"ok"}`), // 响应体(支持 string 或 []byte
model.SHA256, // 哈希算法
time.Now(), // 操作时间戳 time.Now(), // 操作时间戳
) )
``` ```
@@ -333,23 +335,18 @@ envelopeConfig := model.DefaultEnvelopeConfig(privateKeyHex, publicKeyHex)
client := highclient.NewClient(pub, myLogger, envelopeConfig) client := highclient.NewClient(pub, myLogger, envelopeConfig)
defer client.Close() defer client.Close()
// 构造 DataID
dataID := model.DataID{
DoPrefix: "10.1000",
DoRepository: "my-repo",
Doid: "10.1000/my-repo/object123",
}
// 构造完整的 Operation // 构造完整的 Operation
op, err := model.NewFullOperation( op, err := model.NewFullOperation(
model.OpSourceDOIP, // 操作来源DOIP 或 IRP model.OpSourceDOIP, // 操作来源DOIP 或 IRP
model.OpTypeCreate, // 操作类型:Create, Update, Delete 等 string(model.OpTypeCreate), // 操作类型:字符串
dataID, // 数据标识 "10.1000", // doPrefix
"user123", // 操作者 "my-repo", // doRepository
"10.1000/my-repo/object123", // doid完整标识
"producer-123", // producerID
"user123", // opActor
[]byte(`{"foo":"bar"}`), // 请求体 []byte(`{"foo":"bar"}`), // 请求体
[]byte(`{"status":"ok"}`), // 响应体 []byte(`{"status":"ok"}`), // 响应体
model.Sha256Simd, // 哈希算法 time.Now(), // 操作时间戳
time.Now(), // 操作时间
) )
if err != nil { if err != nil {
panic(err) panic(err)
@@ -454,13 +451,13 @@ import (
ctx := context.Background() ctx := context.Background()
// 构造查询请求 // 构造查询请求
req := queryclient.ListOperationsRequest{ req := queryclient.ListRequest{
PageSize: 100, // 每页数量 PageSize: 100, // 每页数量
PreTime: time.Now().Add(-24 * time.Hour), // 游标分页(可选) PreTime: time.Now().Add(-24 * time.Hour), // 游标分页(可选)
// 可选过滤条件 // 可选过滤条件
OpSource: model.OpSourceDOIP, // 按操作来源过滤 OpSource: "DOIP", // 按操作来源过滤(字符串)
OpType: model.OpTypeCreate, // 按操作类型过滤 OpType: "Create", // 按操作类型过滤(字符串)
DoPrefix: "10.1000", // 按数据前缀过滤 DoPrefix: "10.1000", // 按数据前缀过滤
DoRepository: "my-repo", // 按仓库过滤 DoRepository: "my-repo", // 按仓库过滤
} }
@@ -475,15 +472,15 @@ if err != nil {
fmt.Printf("Total count: %d\n", resp.Count) fmt.Printf("Total count: %d\n", resp.Count)
for _, op := range resp.Data { for _, op := range resp.Data {
fmt.Printf("Operation ID: %s, Type: %s, Time: %s\n", fmt.Printf("Operation ID: %s, Type: %s, Time: %s\n",
op.Meta.OpID, op.Meta.OpType, op.Meta.Timestamp) op.OpID, op.OpType, op.Timestamp)
} }
``` ```
#### 2.3 取证验证(流式) #### 2.3 取证验证(流式)
```go ```go
// 构造验证请求 // 构造验证请求
validationReq := queryclient.ValidationRequest{ validationReq := queryclient.ValidateRequest{
Time: time.Now().Add(-1 * time.Hour), Timestamp: time.Now().Add(-1 * time.Hour),
OpID: "operation-id-123", OpID: "operation-id-123",
OpType: "Create", OpType: "Create",
DoRepository: "my-repo", DoRepository: "my-repo",
@@ -535,7 +532,7 @@ if finalResult.IsCompleted() {
#### 2.5 查询记录列表Record #### 2.5 查询记录列表Record
```go ```go
// 构造记录查询请求 // 构造记录查询请求
recordReq := queryclient.ListRecordsRequest{ recordReq := queryclient.ListRequest{
PageSize: 50, // 每页数量 PageSize: 50, // 每页数量
PreTime: time.Now().Add(-24 * time.Hour), // 游标分页(可选) PreTime: time.Now().Add(-24 * time.Hour), // 游标分页(可选)
@@ -561,7 +558,7 @@ for _, rec := range recordResp.Data {
#### 2.6 记录验证(流式) #### 2.6 记录验证(流式)
```go ```go
// 构造记录验证请求 // 构造记录验证请求
recordValidationReq := queryclient.RecordValidationRequest{ recordValidationReq := queryclient.ValidateRequest{
Timestamp: time.Now().Add(-1 * time.Hour), Timestamp: time.Now().Add(-1 * time.Hour),
RecordID: "record-id-123", RecordID: "record-id-123",
DoPrefix: "10.1000", DoPrefix: "10.1000",
@@ -638,20 +635,46 @@ import (
"time" "time"
"go.yandata.net/iod/iod/go-trustlog/api/persistence" "go.yandata.net/iod/iod/go-trustlog/api/persistence"
"go.yandata.net/iod/iod/go-trustlog/api/adapter"
"go.yandata.net/iod/iod/go-trustlog/api/model" "go.yandata.net/iod/iod/go-trustlog/api/model"
"go.yandata.net/iod/iod/go-trustlog/api/logger"
"github.com/go-logr/logr"
) )
func main() { func main() {
ctx := context.Background() ctx := context.Background()
// 创建 Persistence Client // 1. 创建 Logger
myLogger := logger.NewLogger(logr.Discard())
// 2. 创建 Pulsar Publisher
publisher, err := adapter.NewPublisher(
adapter.PublisherConfig{
URL: "pulsar://localhost:6650",
},
myLogger,
)
if err != nil {
panic(err)
}
defer publisher.Close()
// 3. 准备 SM2 密钥和 Envelope 配置(用于签名)
privateKeyHex := []byte("私钥D的十六进制字符串")
publicKeyHex := []byte("04 + x坐标 + y坐标的十六进制字符串")
envelopeConfig := model.DefaultEnvelopeConfig(privateKeyHex, publicKeyHex)
// 4. 创建 Persistence Client
client, err := persistence.NewPersistenceClient(ctx, persistence.PersistenceClientConfig{ client, err := persistence.NewPersistenceClient(ctx, persistence.PersistenceClientConfig{
Publisher: publisher, // Pulsar Publisher Publisher: publisher, // Pulsar Publisher
Logger: logger, Logger: myLogger,
EnvelopeConfig: envelopeConfig, // SM2 签名配置 EnvelopeConfig: envelopeConfig, // SM2 签名配置
DBConfig: persistence.DBConfig{ DBConfig: persistence.DBConfig{
DriverName: "postgres", DriverName: "postgres",
DSN: "postgres://user:pass@localhost:5432/trustlog?sslmode=disable", DSN: "postgres://user:pass@localhost:5432/trustlog?sslmode=disable",
MaxOpenConns: 20,
MaxIdleConns: 10,
ConnMaxLifetime: time.Hour,
}, },
PersistenceConfig: persistence.PersistenceConfig{ PersistenceConfig: persistence.PersistenceConfig{
Strategy: persistence.StrategyDBAndTrustlog, // 既落库又存证 Strategy: persistence.StrategyDBAndTrustlog, // 既落库又存证
@@ -675,18 +698,20 @@ func main() {
} }
defer client.Close() defer client.Close()
// 发布操作(立即返回,异步存证) // 5. 发布操作(立即返回,异步存证)
clientIP := "192.168.1.100" clientIP := "192.168.1.100"
serverIP := "10.0.0.1" serverIP := "10.0.0.1"
op := &model.Operation{ op := &model.Operation{
OpID: "op-001", OpID: "op-001",
OpType: model.OpTypeCreate, OpType: string(model.OpTypeCreate), // 字符串类型
Doid: "10.1000/repo/obj", Doid: "10.1000/repo/obj",
ProducerID: "producer-001", ProducerID: "producer-001",
OpSource: model.OpSourceDOIP, OpSource: model.OpSourceDOIP,
DoPrefix: "10.1000", DoPrefix: "10.1000",
DoRepository: "repo", DoRepository: "repo",
OpActor: "user-123",
Timestamp: time.Now(),
ClientIP: &clientIP, // 可空 ClientIP: &clientIP, // 可空
ServerIP: &serverIP, // 可空 ServerIP: &serverIP, // 可空
} }
@@ -695,18 +720,23 @@ func main() {
panic(err) panic(err)
} }
// ✅ 落库成功CursorWorker 会自动异步存证 // ✅ 落库成功CursorWorker 会自动异步存证(带签名)
println("操作已保存,正在异步存证...") println("操作已保存,正在异步存证...")
} }
``` ```
#### 3.2 三种持久化策略 #### 3.2 三种持久化策略
| 策略 | 说明 | 适用场景 | | 策略 | 说明 | 是否签名 | 适用场景 |
|------|------|----------| |------|------|---------|----------|
| **StrategyDBOnly** | 仅落库,不存证 | 历史数据存档、审计日志 | | **StrategyDBOnly** | 仅落库,不存证 | ❌ 不签名 | 历史数据存档、审计日志 |
| **StrategyDBAndTrustlog** | 既落库又存证(异步) | ⭐ 生产环境推荐 | | **StrategyDBAndTrustlog** | 既落库又存证(异步) |**签名存证** | ⭐ 生产环境推荐 |
| **StrategyTrustlogOnly** | 仅存证,不落库 | 轻量级场景 | | **StrategyTrustlogOnly** | 仅存证,不落库 |**签名存证** | 轻量级场景 |
**重要说明**
- 所有存证操作(`StrategyDBAndTrustlog``StrategyTrustlogOnly`)都会使用 `EnvelopeConfig` 进行 **SM2 签名**
- `StrategyDBOnly` 仅保存到数据库,不会进行签名和存证
- 创建 `PersistenceClient` 时**必须**提供 `EnvelopeConfig`(即使是 `StrategyDBOnly` 也建议提供,以便后续切换策略)
#### 3.3 Cursor + Retry 双层架构 #### 3.3 Cursor + Retry 双层架构
@@ -717,13 +747,13 @@ func main() {
CursorWorker第一道防线 CursorWorker第一道防线
├── 增量扫描 operation 表 ├── 增量扫描 operation 表
├── 快速尝试存证 ├── 快速尝试存证(使用 Envelope 签名)✅
├── 成功 → 更新状态 ├── 成功 → 更新状态
└── 失败 → 加入 retry 表 └── 失败 → 加入 retry 表
RetryWorker第二道防线 RetryWorker第二道防线
├── 扫描 retry 表 ├── 扫描 retry 表
├── 指数退避重试 ├── 指数退避重试(使用 Envelope 签名)✅
├── 成功 → 删除 retry 记录 ├── 成功 → 删除 retry 记录
└── 失败 → 标记死信 └── 失败 → 标记死信
``` ```
@@ -733,6 +763,7 @@ RetryWorker第二道防线
- ✅ 双层保障确保最终一致性 - ✅ 双层保障确保最终一致性
- ✅ 性能优秀,扩展性强 - ✅ 性能优秀,扩展性强
- ✅ 监控清晰,易于维护 - ✅ 监控清晰,易于维护
-**所有存证操作都经过 SM2 签名验证**
#### 3.4 数据库表设计 #### 3.4 数据库表设计
@@ -970,13 +1001,15 @@ func main() {
op, _ := model.NewFullOperation( op, _ := model.NewFullOperation(
model.OpSourceDOIP, model.OpSourceDOIP,
model.OpTypeCreate, string(model.OpTypeCreate), // 字符串类型
dataID, "10.1000", // doPrefix
"admin", "test-repo", // doRepository
[]byte(`{"action":"create"}`), "10.1000/test-repo/doc001", // doid
[]byte(`{"status":"success"}`), "producer-001", // producerID
model.SHA256, "admin", // opActor
time.Now(), []byte(`{"action":"create"}`), // requestBody
[]byte(`{"status":"success"}`), // responseBody
time.Now(), // timestamp
) )
_ = client.OperationPublish(op) _ = client.OperationPublish(op)
@@ -992,7 +1025,7 @@ func main() {
) )
defer queryClient.Close() defer queryClient.Close()
listResp, _ := queryClient.ListOperations(ctx, queryclient.ListOperationsRequest{ listResp, _ := queryClient.ListOperations(ctx, queryclient.ListRequest{
PageSize: 10, PageSize: 10,
DoRepository: "test-repo", DoRepository: "test-repo",
}) })
@@ -1003,11 +1036,11 @@ func main() {
if len(listResp.Data) > 0 { if len(listResp.Data) > 0 {
firstOp := listResp.Data[0] firstOp := listResp.Data[0]
validationReq := queryclient.ValidationRequest{ validationReq := queryclient.ValidateRequest{
Time: firstOp.Meta.Timestamp, Timestamp: firstOp.Timestamp,
OpID: firstOp.Meta.OpID, OpID: firstOp.OpID,
OpType: string(firstOp.Meta.OpType), OpType: firstOp.OpType, // 已经是字符串
DoRepository: firstOp.DataID.DoRepository, DoRepository: firstOp.DoRepository,
} }
result, _ := queryClient.ValidateOperationSync(ctx, validationReq, nil) result, _ := queryClient.ValidateOperationSync(ctx, validationReq, nil)
@@ -1027,17 +1060,22 @@ func main() {
### DOIP 操作类型7种 ### DOIP 操作类型7种
```go ```go
model.OpTypeHello // Hello 握手 // 使用时需要转换为字符串
model.OpTypeRetrieve // 检索资源 string(model.OpTypeHello) // "Hello"
model.OpTypeCreate // 新建资源 string(model.OpTypeRetrieve) // "Retrieve"
model.OpTypeDelete // 删除资源 string(model.OpTypeCreate) // "Create"
model.OpTypeUpdate // 更新资源 string(model.OpTypeDelete) // "Delete"
model.OpTypeSearch // 搜索资源 string(model.OpTypeUpdate) // "Update"
model.OpTypeListOperations // 列出可用操作 string(model.OpTypeSearch) // "Search"
string(model.OpTypeListOperations) // "ListOperations"
``` ```
### IRP 操作类型33种 ### IRP 操作类型33种
```go ```go
// 使用时需要转换为字符串,例如:
string(model.OpTypeOCCreateHandle) // "OC_CREATE_HANDLE"
string(model.OpTypeOCDeleteHandle) // "OC_DELETE_HANDLE"
// Handle 基础操作 // Handle 基础操作
model.OpTypeOCReserved, model.OpTypeOCResolution, model.OpTypeOCGetSiteInfo model.OpTypeOCReserved, model.OpTypeOCResolution, model.OpTypeOCGetSiteInfo
model.OpTypeOCCreateHandle, model.OpTypeOCDeleteHandle, model.OpTypeOCAddValue model.OpTypeOCCreateHandle, model.OpTypeOCDeleteHandle, model.OpTypeOCAddValue
@@ -1179,13 +1217,13 @@ model.OpTypeOCQueryRouter
[CursorWorker每10秒] [CursorWorker每10秒]
├── 增量扫描 operation 表 ├── 增量扫描 operation 表
├── 尝试发送到存证系统 ├── 尝试发送到存证系统Envelope 签名)✅
├── 成功 → 更新状态为 TRUSTLOGGED ├── 成功 → 更新状态为 TRUSTLOGGED
└── 失败 → 加入 trustlog_retry 表 └── 失败 → 加入 trustlog_retry 表
[RetryWorker每30秒] [RetryWorker每30秒]
├── 扫描 trustlog_retry 表 ├── 扫描 trustlog_retry 表
├── 指数退避重试(1m → 2m → 4m → 8m → 16m ├── 指数退避重试(Envelope 签名)✅
├── 成功 → 删除 retry 记录 ├── 成功 → 删除 retry 记录
└── 失败 → 标记为 DEAD_LETTER └── 失败 → 标记为 DEAD_LETTER
@@ -1194,6 +1232,7 @@ model.OpTypeOCQueryRouter
- ✅ 双层保障确保最终一致性 - ✅ 双层保障确保最终一致性
- ✅ 性能优秀(增量扫描 + 索引查询) - ✅ 性能优秀(增量扫描 + 索引查询)
- ✅ 易于监控和运维 - ✅ 易于监控和运维
- ✅ 所有存证操作都经过 SM2 签名验证
``` ```
--- ---