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) }