Files
go-trustlog/api/model/record.go
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

349 lines
8.1 KiB
Go
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.
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
}