docs: 修复 README 示例与当前 API 的一致性 - OpType改为string,NewFullOperation参数修正,查询API统一,Persistence完整示例,明确所有存证策略都支持SM2签名
This commit is contained in:
159
README.md
159
README.md
@@ -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 签名验证
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user