88 lines
2.4 KiB
Go
88 lines
2.4 KiB
Go
package ingest
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/binary"
|
||
"strings"
|
||
)
|
||
|
||
// 本文件是 backend_v2/internal/pkg/hashid 中「无类型编码(品牌等)」的忠实移植,
|
||
// 仅用于 spider 离线 Mock 模式下生成 brand_uid(真实上报路径下 brand_uid 直接来自后台接口,无需自编码)。
|
||
// 算法:SHA256(盐) 派生 4 个子密钥 → 32-bit 平衡 Feistel 网络 → base62 定长(minLen=8) 序列化。
|
||
// 默认盐与后台一致:hashid_secret 为空时后台走 "fashion-archive-default-salt-change-me"。
|
||
|
||
const (
|
||
hidAlphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||
hidBase = 62
|
||
hidMinLen = 8
|
||
)
|
||
|
||
var hidKeys [4]uint32
|
||
|
||
// InitHashID 用部署级盐值初始化混淆密钥。盐为空时使用与后台相同的内置默认盐。
|
||
func InitHashID(secret string) {
|
||
if secret == "" {
|
||
secret = "fashion-archive-default-salt-change-me"
|
||
}
|
||
h := sha256.Sum256([]byte(secret))
|
||
for i := 0; i < 4; i++ {
|
||
hidKeys[i] = binary.BigEndian.Uint32(h[i*4 : i*4+4])
|
||
}
|
||
}
|
||
|
||
// feistel 32-bit 平衡 Feistel 网络(全局密钥版,品牌等无类型实体用)。
|
||
func feistel(v uint32, encrypt bool) uint32 {
|
||
return feistelK(v, encrypt, hidKeys)
|
||
}
|
||
|
||
func feistelK(v uint32, encrypt bool, k [4]uint32) uint32 {
|
||
const rounds = 8
|
||
l, r := uint16(v>>16), uint16(v&0xffff)
|
||
for i := 0; i < rounds; i++ {
|
||
idx := i
|
||
if !encrypt {
|
||
idx = rounds - 1 - i
|
||
}
|
||
round := func(h uint16) uint16 {
|
||
f := uint32(h)*0x9E3779B1 + k[idx%4]
|
||
return uint16((f ^ (f >> 16)) & 0xffff)
|
||
}
|
||
if encrypt {
|
||
nl := r
|
||
nr := l ^ round(r)
|
||
l, r = nl, nr
|
||
} else {
|
||
nl := r ^ round(l)
|
||
nr := l
|
||
l, r = nl, nr
|
||
}
|
||
}
|
||
return uint32(l)<<16 | uint32(r)
|
||
}
|
||
|
||
// numToBase62 把 32-bit 值序列化为定长(minLen)base62 串,高位在前。
|
||
func numToBase62(x uint32) string {
|
||
var sb strings.Builder
|
||
for x > 0 {
|
||
sb.WriteByte(hidAlphabet[x%hidBase])
|
||
x /= hidBase
|
||
}
|
||
if sb.Len() == 0 {
|
||
sb.WriteByte(hidAlphabet[0])
|
||
}
|
||
runes := []rune(sb.String())
|
||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||
runes[i], runes[j] = runes[j], runes[i]
|
||
}
|
||
out := string(runes)
|
||
if len(out) < hidMinLen {
|
||
out = strings.Repeat("0", hidMinLen-len(out)) + out
|
||
}
|
||
return out
|
||
}
|
||
|
||
// EncodeBrand 把数字品牌主键编码为对外 hashid 串(与后台 hashid.Encode 同算法、同盐)。
|
||
func EncodeBrand(id uint32) string {
|
||
return numToBase62(feistel(id, true))
|
||
}
|