package auth import ( "errors" "fmt" "sync" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) // User 用户数据 type User struct { ID string `json:"id"` Username string `json:"username"` PasswordHash string `json:"-"` // 不返回给客户端 CreatedAt int64 `json:"created_at"` Level int `json:"level"` Exp int `json:"exp"` Diamonds int `json:"diamonds"` RedstoneCoins int64 `json:"redstone_coins"` GoldCoins int `json:"gold_coins"` LastLoginDate string `json:"last_login_date"` Inventory map[string]int `json:"inventory"` // itemId → count EquippedItem string `json:"equipped_item"` // 当前装备的道具ID SpeedBoostUntil int64 `json:"speed_boost_until"` // 速度加成到期时间(unix) PurchaseHistory []PurchaseRecord `json:"purchase_history"` // 最近50条购买记录 } // PurchaseRecord 购买记录 type PurchaseRecord struct { ItemID string `json:"item_id"` ItemName string `json:"item_name"` Currency string `json:"currency"` Amount int64 `json:"amount"` Timestamp int64 `json:"timestamp"` } // UserStore 内存用户存储 (后续可替换为 PostgreSQL) type UserStore struct { mu sync.RWMutex users map[string]*User // key = username (lowercase) } func NewUserStore() *UserStore { return &UserStore{users: make(map[string]*User)} } func (s *UserStore) Create(username, password string) (*User, error) { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.users[username]; exists { return nil, errors.New("用户名已存在") } hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return nil, fmt.Errorf("密码加密失败: %w", err) } now := time.Now() user := &User{ ID: fmt.Sprintf("u_%d", now.UnixNano()), Username: username, PasswordHash: string(hash), CreatedAt: now.Unix(), Level: 1, Exp: 0, Diamonds: 20, RedstoneCoins: 3000, GoldCoins: 1, LastLoginDate: "", // 留空:注册当天可正常签到领奖 Inventory: make(map[string]int), PurchaseHistory: []PurchaseRecord{}, } s.users[username] = user return user, nil } func (s *UserStore) GetByUsername(username string) (*User, bool) { s.mu.RLock() defer s.mu.RUnlock() u, ok := s.users[username] return u, ok } func (s *UserStore) GetByID(id string) (*User, bool) { s.mu.RLock() defer s.mu.RUnlock() for _, u := range s.users { if u.ID == id { return u, true } } return nil, false } func (s *UserStore) VerifyPassword(username, password string) (*User, error) { user, ok := s.GetByUsername(username) if !ok { return nil, errors.New("用户不存在") } if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil { return nil, errors.New("密码错误") } return user, nil } // DailyLogin 每日登录奖励。返回奖励详情和是否已领取。 func (s *UserStore) DailyLogin(userID string) (map[string]interface{}, error) { s.mu.Lock() defer s.mu.Unlock() var user *User for _, u := range s.users { if u.ID == userID { user = u break } } if user == nil { return nil, errors.New("用户不存在") } today := time.Now().Format("2006-01-02") if user.LastLoginDate == today { return map[string]interface{}{ "claimed": true, "message": "今日已领取", }, nil } // 发放奖励 const rewardDiamonds = 20 const rewardRedstoneCoins = 3000 const rewardGoldCoins = 1 user.Diamonds += rewardDiamonds user.RedstoneCoins += rewardRedstoneCoins user.GoldCoins += rewardGoldCoins user.LastLoginDate = today return map[string]interface{}{ "claimed": false, "message": "领取成功", "diamonds": rewardDiamonds, "redstone": rewardRedstoneCoins, "gold": rewardGoldCoins, "total_diamonds": user.Diamonds, "total_redstone": user.RedstoneCoins, "total_gold": user.GoldCoins, }, nil } // SpendCurrency 消费货币,返回成功或余额不足错误 func (s *UserStore) SpendCurrency(userID, currency string, amount int64) error { s.mu.Lock() defer s.mu.Unlock() var user *User for _, u := range s.users { if u.ID == userID { user = u break } } if user == nil { return errors.New("用户不存在") } switch currency { case "diamonds": if user.Diamonds < int(amount) { return fmt.Errorf("钻石不足,当前: %d,需要: %d", user.Diamonds, amount) } user.Diamonds -= int(amount) case "redstone_coins": if user.RedstoneCoins < amount { return fmt.Errorf("红石币不足,当前: %d,需要: %d", user.RedstoneCoins, amount) } user.RedstoneCoins -= amount case "gold_coins": if user.GoldCoins < int(amount) { return fmt.Errorf("金币不足,当前: %d,需要: %d", user.GoldCoins, amount) } user.GoldCoins -= int(amount) default: return fmt.Errorf("未知货币类型: %s", currency) } return nil } // BuyItem 购买商品 (货币扣款 + 库存追踪 + 交易记录) func (s *UserStore) BuyItem(userID, itemID, itemName, currency string, amount int64, quantity int) (map[string]interface{}, error) { s.mu.Lock() defer s.mu.Unlock() var user *User for _, u := range s.users { if u.ID == userID { user = u; break } } if user == nil { return nil, errors.New("用户不存在") } switch currency { case "diamonds": if user.Diamonds < int(amount) { return nil, fmt.Errorf("钻石不足") }; user.Diamonds -= int(amount) case "redstone_coins": if user.RedstoneCoins < amount { return nil, fmt.Errorf("红石币不足") }; user.RedstoneCoins -= amount case "gold_coins": if user.GoldCoins < int(amount) { return nil, fmt.Errorf("金币不足") }; user.GoldCoins -= int(amount) default: return nil, fmt.Errorf("未知货币") } if user.Inventory == nil { user.Inventory = make(map[string]int) } user.Inventory[itemID] += quantity if itemID == "speed_boost" { user.SpeedBoostUntil = time.Now().Unix() + 3600 } rec := PurchaseRecord{ItemID: itemID, ItemName: itemName, Currency: currency, Amount: amount, Timestamp: time.Now().Unix()} user.PurchaseHistory = append([]PurchaseRecord{rec}, user.PurchaseHistory...) if len(user.PurchaseHistory) > 50 { user.PurchaseHistory = user.PurchaseHistory[:50] } return map[string]interface{}{"message": "购买成功", "item_id": itemID, "quantity": quantity, "user": userToMap(user)}, nil } // UseItem 使用道具 func (s *UserStore) UseItem(userID, itemID string) (map[string]interface{}, error) { s.mu.Lock(); defer s.mu.Unlock() var user *User for _, u := range s.users { if u.ID == userID { user = u; break } } if user == nil { return nil, errors.New("用户不存在") } if user.Inventory == nil || user.Inventory[itemID] <= 0 { return nil, errors.New("道具数量不足") } user.Inventory[itemID]-- msg := "使用成功" switch itemID { case "speed_boost": user.SpeedBoostUntil = time.Now().Unix() + 3600 user.EquippedItem = itemID msg = "速度加成已激活(1小时)" case "exp_boost": user.Exp += 500 if user.Exp >= user.Level*1000 { user.Level++ user.Exp -= (user.Level - 1) * 1000 msg = fmt.Sprintf("升级! Lv.%d", user.Level) } else { msg = "获得500经验值" } case "diamond_pack": user.Diamonds += 10 msg = "获得10钻石" case "gold_pack": user.GoldCoins += 5 msg = "获得5金币" case "rs_pack": user.RedstoneCoins += 10000 msg = "获得10000红石币" } return map[string]interface{}{"message": msg, "user": userToMap(user)}, nil } // userToMap 将 User 转换为返回给客户端的 map func userToMap(u *User) map[string]interface{} { inv := u.Inventory if inv == nil { inv = make(map[string]int) } hist := u.PurchaseHistory if hist == nil { hist = []PurchaseRecord{} } return map[string]interface{}{ "id": u.ID, "username": u.Username, "level": u.Level, "exp": u.Exp, "diamonds": u.Diamonds, "redstone_coins": u.RedstoneCoins, "gold_coins": u.GoldCoins, "last_login_date": u.LastLoginDate, "created_at": u.CreatedAt, "inventory": inv, "equipped_item": u.EquippedItem, "speed_boost_until": u.SpeedBoostUntil, "purchase_history": hist, } } // ==================== JWT ==================== type JWTManager struct { secretKey []byte expires time.Duration } func NewJWTManager(secret string) *JWTManager { return &JWTManager{ secretKey: []byte(secret), expires: 24 * time.Hour, } } type Claims struct { UserID string `json:"uid"` Username string `json:"username"` jwt.RegisteredClaims } func (m *JWTManager) Generate(user *User) (string, error) { claims := &Claims{ UserID: user.ID, Username: user.Username, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(m.expires)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(m.secretKey) } func (m *JWTManager) Verify(tokenStr string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } return m.secretKey, nil }) if err != nil { return nil, err } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, errors.New("invalid token") } return claims, nil }