82 lines
2.0 KiB
Go
82 lines
2.0 KiB
Go
package ingest
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/binary"
|
||
"strings"
|
||
)
|
||
|
||
// 以下 Feistel + base62 编码与后台 internal/pkg/hashid 完全一致,
|
||
// 仅实现编码侧(爬虫只需把品牌数字主键编码为 brand_uid 上送,无需解码)。
|
||
// 盐值必须 == 后台 HASHID_SECRET,否则生成的 brand_uid 后台解码不出正确主键。
|
||
|
||
const (
|
||
hidAlphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||
hidBase = 62
|
||
hidMinLen = 8
|
||
)
|
||
|
||
var hidKeys [4]uint32
|
||
|
||
// InitHashID 用与后台 HASHID_SECRET 相同的盐值初始化编码密钥。
|
||
// secret 为空时回落后台内置默认盐(仅防顺序枚举,不安全)。
|
||
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])
|
||
}
|
||
}
|
||
|
||
// hidFeistel 32-bit 平衡 Feistel 网络,encrypt=true 加密。
|
||
func hidFeistel(v uint32, encrypt bool) 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 + hidKeys[idx%4]
|
||
return uint16((f ^ (f >> 16)) & 0xffff)
|
||
}
|
||
if encrypt {
|
||
nl, nr := r, uint16(l)^round(r)
|
||
l, r = nl, nr
|
||
} else {
|
||
nl, nr := uint16(l)^round(l), l
|
||
l, r = nl, nr
|
||
}
|
||
}
|
||
return uint32(l)<<16 | uint32(r)
|
||
}
|
||
|
||
func encodeNum(n uint32) string {
|
||
x := hidFeistel(n, true)
|
||
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 串(与后台 brand_uid 同算法)。
|
||
func EncodeBrand(id uint32) string {
|
||
return encodeNum(id)
|
||
}
|