商城c端前端

This commit is contained in:
richjohnrambo
2026-10-12 20:29:27 +08:00
parent 2d411e1b44
commit 8f3a97a54d
550 changed files with 304 additions and 68927 deletions
+14 -69
View File
@@ -2,9 +2,6 @@ package accounts
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
@@ -47,7 +44,7 @@ var (
type Repository interface {
RegisterAccount(context.Context, store.AccountRegistration) (*store.Account, error)
AccountByMobileHash(context.Context, int64, string) (*store.Account, error)
AccountByMobile(context.Context, int64, string) (*store.Account, error)
RecordLogin(context.Context, int64, int64) error
MemberOverview(context.Context, int64, int64) (*store.MemberOverview, error)
UpdateAvatar(context.Context, int64, int64, int64) (*store.Avatar, error)
@@ -81,7 +78,7 @@ type Profile struct {
MemberID int64 `json:"member_id"`
MemberNo string `json:"member_no"`
Username string `json:"username"`
PhoneMask string `json:"phone_mask"`
Phone string `json:"phone"`
AvatarURL string `json:"avatar_url"`
InviteCode string `json:"invite_code"`
Level ProfileLevel `json:"level"`
@@ -129,30 +126,16 @@ type Service struct {
repository Repository
redis *redis.Client
cfg config.AuthConfig
aead cipher.AEAD
lookupKey []byte
ready bool
}
func New(repository Repository, redisClient *redis.Client, cfg config.AuthConfig) *Service {
service := &Service{repository: repository, redis: redisClient, cfg: cfg}
encryptionKey, encryptionErr := decodeKey(cfg.DataEncryptionKey)
lookupKey, lookupErr := decodeKey(cfg.LookupHMACKey)
if encryptionErr != nil || len(encryptionKey) != 32 || lookupErr != nil || len(lookupKey) < 32 {
return service
return &Service{
repository: repository,
redis: redisClient,
cfg: cfg,
ready: repository != nil && redisClient != nil,
}
block, err := aes.NewCipher(encryptionKey)
if err != nil {
return service
}
aead, err := cipher.NewGCM(block)
if err != nil {
return service
}
service.aead = aead
service.lookupKey = lookupKey
service.ready = repository != nil && redisClient != nil
return service
}
func (s *Service) Register(ctx context.Context, input RegisterInput) (*Result, error) {
@@ -178,12 +161,6 @@ func (s *Service) Register(ctx context.Context, input RegisterInput) (*Result, e
return nil, ErrPrivacyNotAccepted
}
normalizedPhone := "+86" + input.Phone
mobileCipher, err := s.encrypt(normalizedPhone)
if err != nil {
return nil, err
}
mobileHash := s.lookupHash(normalizedPhone)
passwordHash, err := bcrypt.GenerateFromPassword([]byte(input.Password), bcrypt.DefaultCost)
if err != nil {
return nil, err
@@ -200,9 +177,7 @@ func (s *Service) Register(ctx context.Context, input RegisterInput) (*Result, e
TenantID: s.cfg.DefaultTenantID,
MemberNo: memberNo,
Username: input.Username,
MobileCipher: mobileCipher,
MobileHash: mobileHash,
MobileMask: maskPhone(input.Phone),
Mobile: input.Phone,
PasswordHash: string(passwordHash),
ReferralCode: input.ReferralCode,
InviteCode: inviteCode,
@@ -227,12 +202,11 @@ func (s *Service) Login(ctx context.Context, input LoginInput) (*Result, error)
if input.Password == "" || len([]byte(input.Password)) > 72 {
return nil, store.ErrInvalidCredentials
}
mobileHash := s.lookupHash("+86" + phone)
rateKey := s.loginRateKey(mobileHash)
rateKey := s.loginRateKey(phone)
if attempts, err := s.redis.Get(ctx, rateKey).Int(); err == nil && attempts >= 10 {
return nil, ErrRateLimited
}
account, err := s.repository.AccountByMobileHash(ctx, s.cfg.DefaultTenantID, mobileHash)
account, err := s.repository.AccountByMobile(ctx, s.cfg.DefaultTenantID, phone)
if err != nil {
if errors.Is(err, store.ErrInvalidCredentials) {
s.recordLoginFailure(ctx, rateKey)
@@ -263,7 +237,7 @@ func (s *Service) Profile(ctx context.Context, tenantID, memberID int64) (*Profi
}
return &Profile{
MemberID: row.MemberID, MemberNo: row.MemberNo, Username: row.Username,
PhoneMask: row.MobileMask, AvatarURL: row.AvatarURL, InviteCode: row.InviteCode,
Phone: row.Mobile, AvatarURL: row.AvatarURL, InviteCode: row.InviteCode,
Level: ProfileLevel{ID: row.LevelID, Code: row.LevelCode, Name: row.LevelName,
IconURL: row.LevelIconURL, CommissionRate: row.CommissionRate},
MemberStatus: memberStatusName(row.MemberStatus), MemberCategory: memberCategoryName(row.MemberCategory),
@@ -379,23 +353,9 @@ func (s *Service) issueSession(ctx context.Context, account *store.Account, devi
return &Result{Account: account, Token: token, ExpiresAt: expiresAt}, nil
}
func (s *Service) encrypt(value string) (string, error) {
nonce := make([]byte, s.aead.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return "", err
}
sealed := s.aead.Seal(nil, nonce, []byte(value), nil)
return base64.RawStdEncoding.EncodeToString(append(nonce, sealed...)), nil
}
func (s *Service) lookupHash(value string) string {
mac := hmac.New(sha256.New, s.lookupKey)
_, _ = mac.Write([]byte(value))
return hex.EncodeToString(mac.Sum(nil))
}
func (s *Service) loginRateKey(mobileHash string) string {
return "workbench:auth:login-rate:" + strconv.FormatInt(s.cfg.DefaultTenantID, 10) + ":" + mobileHash
func (s *Service) loginRateKey(phone string) string {
digest := sha256.Sum256([]byte(phone))
return "workbench:auth:login-rate:" + strconv.FormatInt(s.cfg.DefaultTenantID, 10) + ":" + hex.EncodeToString(digest[:])
}
func (s *Service) recordLoginFailure(ctx context.Context, key string) {
@@ -405,26 +365,11 @@ func (s *Service) recordLoginFailure(ctx context.Context, key string) {
_, _ = pipe.Exec(ctx)
}
func decodeKey(value string) ([]byte, error) {
value = strings.TrimSpace(value)
if decoded, err := base64.StdEncoding.DecodeString(value); err == nil {
return decoded, nil
}
return base64.RawStdEncoding.DecodeString(value)
}
func sessionKey(pattern string, rawToken []byte) string {
digest := sha256.Sum256(rawToken)
return strings.Replace(pattern, "%s", hex.EncodeToString(digest[:]), 1)
}
func maskPhone(phone string) string {
if len(phone) != 11 {
return ""
}
return phone[:3] + "****" + phone[7:]
}
func newPublicID(prefix string, length int) (string, error) {
const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
raw := make([]byte, length-len(prefix))