Files
junhong_cmp_fiber/pkg/idempotency/fingerprint.go
break 17782d5f8e
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
实现七月迭代公共技术基础
2026-07-23 17:52:48 +09:00

125 lines
3.5 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 idempotency 提供创建类命令的幂等身份与请求指纹公共契约。
package idempotency
import (
"crypto/sha256"
"encoding/hex"
"reflect"
"strings"
"github.com/bytedance/sonic"
)
const (
// FingerprintAlgorithmV1 是请求指纹算法的首个稳定版本。
FingerprintAlgorithmV1 = "sha256-canonical-json-v1"
)
// Scope 表示请求 ID 的幂等作用域。
type Scope struct {
Subject string `json:"subject"`
Operation string `json:"operation"`
}
// FingerprintValue 表示带算法版本的请求指纹。
type FingerprintValue struct {
Algorithm string `json:"algorithm"`
Value string `json:"value"`
}
// Record 表示业务模块自行持久化的幂等事实最小投影。
type Record struct {
Scope Scope `json:"scope"`
RequestID string `json:"request_id"`
Fingerprint FingerprintValue `json:"fingerprint"`
}
// ReplayKind 表示已有事实与本次命令的关系。
type ReplayKind string
const (
// ReplaySame 表示相同命令重放,应返回原业务结果。
ReplaySame ReplayKind = "same"
// ReplayConflict 表示同作用域、同请求 ID 携带了不同业务内容。
ReplayConflict ReplayKind = "conflict"
// ReplayUnrelated 表示不是同一幂等作用域内的命令。
ReplayUnrelated ReplayKind = "unrelated"
)
var volatileFieldNames = map[string]struct{}{
"authorization": {},
"cookie": {},
"nonce": {},
"sign": {},
"signature": {},
"timestamp": {},
"token": {},
}
// Fingerprint 对影响业务结果的规范化字段计算稳定指纹。
//
// 调用方仍应优先传入专用命令 DTO本函数会递归排除约定的易变传输字段
// 并去除字符串首尾空白,避免签名、令牌或对象字段顺序污染业务身份。
func Fingerprint(command any) (FingerprintValue, error) {
raw, err := sonic.ConfigStd.Marshal(command)
if err != nil {
return FingerprintValue{}, err
}
var value any
if err := sonic.Unmarshal(raw, &value); err != nil {
return FingerprintValue{}, err
}
normalized := normalize(value)
canonical, err := sonic.ConfigStd.Marshal(normalized)
if err != nil {
return FingerprintValue{}, err
}
sum := sha256.Sum256(canonical)
return FingerprintValue{
Algorithm: FingerprintAlgorithmV1,
Value: hex.EncodeToString(sum[:]),
}, nil
}
// ValidateScope 校验幂等作用域和稳定请求 ID 是否完整。
func ValidateScope(scope Scope, requestID string) bool {
return strings.TrimSpace(scope.Subject) != "" &&
strings.TrimSpace(scope.Operation) != "" &&
strings.TrimSpace(requestID) != ""
}
// Classify 比较已有幂等事实与本次命令。
func Classify(existing Record, scope Scope, requestID string, fingerprint FingerprintValue) ReplayKind {
if existing.Scope != scope || existing.RequestID != requestID {
return ReplayUnrelated
}
if reflect.DeepEqual(existing.Fingerprint, fingerprint) {
return ReplaySame
}
return ReplayConflict
}
func normalize(value any) any {
switch typed := value.(type) {
case map[string]any:
result := make(map[string]any, len(typed))
for key, item := range typed {
if _, volatile := volatileFieldNames[strings.ToLower(strings.TrimSpace(key))]; volatile {
continue
}
result[key] = normalize(item)
}
return result
case []any:
result := make([]any, len(typed))
for index, item := range typed {
result[index] = normalize(item)
}
return result
case string:
return strings.TrimSpace(typed)
default:
return value
}
}