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等)预留空间。
This commit is contained in:
348
api/model/record.go
Normal file
348
api/model/record.go
Normal file
@@ -0,0 +1,348 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.yandata.net/iod/iod/trustlog-sdk/api/logger"
|
||||
"go.yandata.net/iod/iod/trustlog-sdk/internal/helpers"
|
||||
)
|
||||
|
||||
// Record 表示一条记录。
|
||||
// 用于记录系统中的操作行为,包含记录标识、节点前缀、操作者信息等。
|
||||
type Record struct {
|
||||
ID string `json:"id" validate:"required,max=128"`
|
||||
DoPrefix string `json:"doPrefix" validate:"max=512"`
|
||||
ProducerID string `json:"producerId" validate:"required,max=512"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Operator string `json:"operator" validate:"max=64"`
|
||||
Extra []byte `json:"extra" validate:"max=512"`
|
||||
RCType string `json:"type" validate:"max=64"`
|
||||
binary []byte
|
||||
}
|
||||
|
||||
//
|
||||
// ===== 构造函数 =====
|
||||
//
|
||||
|
||||
// NewFullRecord 创建包含所有字段的完整 Record。
|
||||
// 自动完成字段校验,确保创建的 Record 是完整且有效的。
|
||||
func NewFullRecord(
|
||||
doPrefix string,
|
||||
producerID string,
|
||||
timestamp time.Time,
|
||||
operator string,
|
||||
extra []byte,
|
||||
rcType string,
|
||||
) (*Record, error) {
|
||||
log := logger.GetGlobalLogger()
|
||||
log.Debug("Creating new full record",
|
||||
"doPrefix", doPrefix,
|
||||
"producerID", producerID,
|
||||
"operator", operator,
|
||||
"rcType", rcType,
|
||||
"extraLength", len(extra),
|
||||
)
|
||||
record := &Record{
|
||||
DoPrefix: doPrefix,
|
||||
ProducerID: producerID,
|
||||
Timestamp: timestamp,
|
||||
Operator: operator,
|
||||
Extra: extra,
|
||||
RCType: rcType,
|
||||
}
|
||||
|
||||
log.Debug("Checking and initializing record")
|
||||
if err := record.CheckAndInit(); err != nil {
|
||||
log.Error("Failed to check and init record",
|
||||
"error", err,
|
||||
)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug("Full record created successfully",
|
||||
"recordID", record.ID,
|
||||
)
|
||||
return record, nil
|
||||
}
|
||||
|
||||
//
|
||||
// ===== 接口实现 =====
|
||||
//
|
||||
|
||||
func (r *Record) Key() string {
|
||||
return r.ID
|
||||
}
|
||||
|
||||
// RecordHashData 实现 HashData 接口,用于存储 Record 的哈希计算结果。
|
||||
type RecordHashData struct {
|
||||
key string
|
||||
hash string
|
||||
}
|
||||
|
||||
func (r RecordHashData) Key() string {
|
||||
return r.key
|
||||
}
|
||||
|
||||
func (r RecordHashData) Hash() string {
|
||||
return r.hash
|
||||
}
|
||||
|
||||
func (r RecordHashData) Type() HashType {
|
||||
return Sha256Simd
|
||||
}
|
||||
|
||||
// DoHash 计算 Record 的整体哈希值,用于数据完整性验证。
|
||||
// 哈希基于序列化后的二进制数据计算,确保记录数据的不可篡改性。
|
||||
func (r *Record) DoHash(_ context.Context) (HashData, error) {
|
||||
log := logger.GetGlobalLogger()
|
||||
log.Debug("Computing hash for record",
|
||||
"recordID", r.ID,
|
||||
)
|
||||
hashTool := GetHashTool(Sha256Simd)
|
||||
binary, err := r.MarshalBinary()
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal record for hash",
|
||||
"error", err,
|
||||
"recordID", r.ID,
|
||||
)
|
||||
return nil, fmt.Errorf("failed to marshal record: %w", err)
|
||||
}
|
||||
|
||||
log.Debug("Computing hash bytes",
|
||||
"recordID", r.ID,
|
||||
"binaryLength", len(binary),
|
||||
)
|
||||
hash, err := hashTool.HashBytes(binary)
|
||||
if err != nil {
|
||||
log.Error("Failed to compute hash",
|
||||
"error", err,
|
||||
"recordID", r.ID,
|
||||
)
|
||||
return nil, fmt.Errorf("failed to compute hash: %w", err)
|
||||
}
|
||||
|
||||
log.Debug("Hash computed successfully",
|
||||
"recordID", r.ID,
|
||||
"hash", hash,
|
||||
)
|
||||
return RecordHashData{
|
||||
key: r.ID,
|
||||
hash: hash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//
|
||||
// ===== CBOR 序列化相关 =====
|
||||
//
|
||||
|
||||
// recordData 用于 CBOR 序列化/反序列化的中间结构。
|
||||
// 排除缓存字段,仅包含可序列化的数据字段。
|
||||
type recordData struct {
|
||||
ID *string `cbor:"id"`
|
||||
DoPrefix *string `cbor:"doPrefix"`
|
||||
ProducerID *string `cbor:"producerId"`
|
||||
Timestamp *time.Time `cbor:"timestamp"`
|
||||
Operator *string `cbor:"operator"`
|
||||
Extra []byte `cbor:"extra"`
|
||||
RCType *string `cbor:"type"`
|
||||
}
|
||||
|
||||
// toRecordData 将 Record 转换为 recordData,用于序列化。
|
||||
func (r *Record) toRecordData() *recordData {
|
||||
return &recordData{
|
||||
ID: &r.ID,
|
||||
DoPrefix: &r.DoPrefix,
|
||||
ProducerID: &r.ProducerID,
|
||||
Timestamp: &r.Timestamp,
|
||||
Operator: &r.Operator,
|
||||
Extra: r.Extra,
|
||||
RCType: &r.RCType,
|
||||
}
|
||||
}
|
||||
|
||||
// fromRecordData 从 recordData 填充 Record,用于反序列化。
|
||||
func (r *Record) fromRecordData(recData *recordData) {
|
||||
if recData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if recData.ID != nil {
|
||||
r.ID = *recData.ID
|
||||
}
|
||||
if recData.DoPrefix != nil {
|
||||
r.DoPrefix = *recData.DoPrefix
|
||||
}
|
||||
if recData.ProducerID != nil {
|
||||
r.ProducerID = *recData.ProducerID
|
||||
}
|
||||
if recData.Timestamp != nil {
|
||||
r.Timestamp = *recData.Timestamp
|
||||
}
|
||||
if recData.Operator != nil {
|
||||
r.Operator = *recData.Operator
|
||||
}
|
||||
if recData.Extra != nil {
|
||||
r.Extra = recData.Extra
|
||||
}
|
||||
if recData.RCType != nil {
|
||||
r.RCType = *recData.RCType
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalBinary 将 Record 序列化为 CBOR 格式的二进制数据。
|
||||
// 实现 encoding.BinaryMarshaler 接口。
|
||||
// 使用 Canonical CBOR 编码确保序列化结果的一致性,使用缓存机制避免重复序列化。
|
||||
func (r *Record) MarshalBinary() ([]byte, error) {
|
||||
log := logger.GetGlobalLogger()
|
||||
log.Debug("Marshaling record to CBOR binary",
|
||||
"recordID", r.ID,
|
||||
)
|
||||
if r.binary != nil {
|
||||
log.Debug("Using cached binary data",
|
||||
"recordID", r.ID,
|
||||
)
|
||||
return r.binary, nil
|
||||
}
|
||||
|
||||
recData := r.toRecordData()
|
||||
|
||||
log.Debug("Marshaling record data to canonical CBOR",
|
||||
"recordID", r.ID,
|
||||
)
|
||||
binary, err := helpers.MarshalCanonical(recData)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal record to CBOR",
|
||||
"error", err,
|
||||
"recordID", r.ID,
|
||||
)
|
||||
return nil, fmt.Errorf("failed to marshal record to CBOR: %w", err)
|
||||
}
|
||||
|
||||
r.binary = binary
|
||||
|
||||
log.Debug("Record marshaled successfully",
|
||||
"recordID", r.ID,
|
||||
"binaryLength", len(binary),
|
||||
)
|
||||
return binary, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary 从 CBOR 格式的二进制数据反序列化为 Record。
|
||||
// 实现 encoding.BinaryUnmarshaler 接口。
|
||||
func (r *Record) UnmarshalBinary(data []byte) error {
|
||||
log := logger.GetGlobalLogger()
|
||||
log.Debug("Unmarshaling record from CBOR binary",
|
||||
"dataLength", len(data),
|
||||
)
|
||||
if len(data) == 0 {
|
||||
log.Error("Data is empty")
|
||||
return errors.New("data is empty")
|
||||
}
|
||||
|
||||
recData := &recordData{}
|
||||
|
||||
log.Debug("Unmarshaling record data from CBOR")
|
||||
if err := helpers.Unmarshal(data, recData); err != nil {
|
||||
log.Error("Failed to unmarshal record from CBOR",
|
||||
"error", err,
|
||||
)
|
||||
return fmt.Errorf("failed to unmarshal record from CBOR: %w", err)
|
||||
}
|
||||
|
||||
r.fromRecordData(recData)
|
||||
|
||||
r.binary = data
|
||||
|
||||
log.Debug("Record unmarshaled successfully",
|
||||
"recordID", r.ID,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDoPrefix 实现 DoPrefixExtractor 接口,返回节点前缀。
|
||||
func (r *Record) GetDoPrefix() string {
|
||||
return r.DoPrefix
|
||||
}
|
||||
|
||||
// GetProducerID 返回 ProducerID,实现 Trustlog 接口。
|
||||
func (r *Record) GetProducerID() string {
|
||||
return r.ProducerID
|
||||
}
|
||||
|
||||
//
|
||||
// ===== 初始化与验证 =====
|
||||
//
|
||||
|
||||
// CheckAndInit 校验并初始化 Record。
|
||||
// 自动填充缺失字段(ID),字段非空验证由 validate 标签处理。
|
||||
func (r *Record) CheckAndInit() error {
|
||||
log := logger.GetGlobalLogger()
|
||||
log.Debug("Checking and initializing record",
|
||||
"producerID", r.ProducerID,
|
||||
"doPrefix", r.DoPrefix,
|
||||
)
|
||||
if r.ID == "" {
|
||||
r.ID = helpers.NewUUIDv7()
|
||||
log.Debug("Generated new record ID",
|
||||
"recordID", r.ID,
|
||||
)
|
||||
}
|
||||
|
||||
if r.Timestamp.IsZero() {
|
||||
r.Timestamp = time.Now()
|
||||
log.Debug("Set default timestamp",
|
||||
"timestamp", r.Timestamp,
|
||||
)
|
||||
}
|
||||
|
||||
log.Debug("Validating record struct")
|
||||
if err := helpers.GetValidator().Struct(r); err != nil {
|
||||
log.Error("Record validation failed",
|
||||
"error", err,
|
||||
"recordID", r.ID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("Record checked and initialized successfully",
|
||||
"recordID", r.ID,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
// ===== 链式调用支持 =====
|
||||
//
|
||||
|
||||
// WithDoPrefix 设置 DoPrefix 并返回自身,支持链式调用。
|
||||
func (r *Record) WithDoPrefix(doPrefix string) *Record {
|
||||
r.DoPrefix = doPrefix
|
||||
return r
|
||||
}
|
||||
|
||||
// WithTimestamp 设置 Timestamp 并返回自身,支持链式调用。
|
||||
func (r *Record) WithTimestamp(timestamp time.Time) *Record {
|
||||
r.Timestamp = timestamp
|
||||
return r
|
||||
}
|
||||
|
||||
// WithOperator 设置 Operator 并返回自身,支持链式调用。
|
||||
func (r *Record) WithOperator(operator string) *Record {
|
||||
r.Operator = operator
|
||||
return r
|
||||
}
|
||||
|
||||
// WithExtra 设置 Extra 并返回自身,支持链式调用。
|
||||
func (r *Record) WithExtra(extra []byte) *Record {
|
||||
r.Extra = extra
|
||||
return r
|
||||
}
|
||||
|
||||
// WithRCType 设置 RCType 并返回自身,支持链式调用。
|
||||
func (r *Record) WithRCType(rcType string) *Record {
|
||||
r.RCType = rcType
|
||||
return r
|
||||
}
|
||||
Reference in New Issue
Block a user