将存储部分迁移到Redis
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"doudizhu-server/internal/models"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRoomNotFound = errors.New("room not found")
|
||||
ErrRoomFull = errors.New("room is full")
|
||||
ErrGameStarted = errors.New("game already started")
|
||||
ErrGameNotStarted = errors.New("game not started")
|
||||
ErrPlayerNotFound = errors.New("player not found")
|
||||
ErrNotEnoughPlayers = errors.New("not enough players")
|
||||
ErrPlayerNotReady = errors.New("player not ready")
|
||||
ErrNotYourTurn = errors.New("not your turn")
|
||||
ErrCardsNotInHand = errors.New("cards not in hand")
|
||||
ErrInvalidCardType = errors.New("invalid card type")
|
||||
ErrCannotBeat = errors.New("cannot beat last play")
|
||||
ErrCannotPass = errors.New("cannot pass")
|
||||
)
|
||||
|
||||
func generateID() string {
|
||||
b := make([]byte, 8)
|
||||
rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
func cardKey(c models.Card) string {
|
||||
return fmt.Sprintf("%d_%d", c.Suit, c.Value)
|
||||
}
|
||||
@@ -1,13 +1,31 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"context"
|
||||
"doudizhu-server/internal/models"
|
||||
"doudizhu-server/internal/redis"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRoomNotFound = errors.New("room not found")
|
||||
ErrRoomFull = errors.New("room is full")
|
||||
ErrGameStarted = errors.New("game already started")
|
||||
ErrGameNotStarted = errors.New("game not started")
|
||||
ErrNotYourTurn = errors.New("not your turn")
|
||||
ErrPlayerNotFound = errors.New("player not found")
|
||||
ErrCardsNotInHand = errors.New("cards not in hand")
|
||||
ErrInvalidCardType = errors.New("invalid card type")
|
||||
ErrCannotBeat = errors.New("cannot beat last play")
|
||||
ErrCannotPass = errors.New("cannot pass")
|
||||
ErrNotEnoughPlayers = errors.New("not enough players")
|
||||
)
|
||||
|
||||
type CardLogic struct{}
|
||||
|
||||
func NewCardLogic() *CardLogic {
|
||||
@@ -227,30 +245,31 @@ func (cl *CardLogic) getMainValue(cards []models.Card) int {
|
||||
}
|
||||
|
||||
type GameManager struct {
|
||||
rooms map[string]*models.Room
|
||||
players map[string]*models.Player
|
||||
deck map[string][]models.Card
|
||||
discard map[string][]models.Card
|
||||
mu sync.RWMutex
|
||||
cl *CardLogic
|
||||
rdb *redis.Client
|
||||
cl *CardLogic
|
||||
mu sync.RWMutex
|
||||
stopCleanup chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
func NewGameManager() *GameManager {
|
||||
return &GameManager{
|
||||
rooms: make(map[string]*models.Room),
|
||||
players: make(map[string]*models.Player),
|
||||
deck: make(map[string][]models.Card),
|
||||
discard: make(map[string][]models.Card),
|
||||
cl: NewCardLogic(),
|
||||
func NewGameManager(rdb *redis.Client) *GameManager {
|
||||
gm := &GameManager{
|
||||
rdb: rdb,
|
||||
cl: NewCardLogic(),
|
||||
stopCleanup: make(chan struct{}),
|
||||
}
|
||||
go gm.cleanupLoop()
|
||||
return gm
|
||||
}
|
||||
|
||||
func (gm *GameManager) CreateRoom(playerName string, maxPlayers int) (*models.Room, *models.Player) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
ctx := context.Background()
|
||||
roomID := generateID()
|
||||
playerID := generateID()
|
||||
|
||||
player := &models.Player{ID: playerID, Name: playerName, IsOnline: true}
|
||||
room := &models.Room{
|
||||
ID: roomID,
|
||||
@@ -262,10 +281,9 @@ func (gm *GameManager) CreateRoom(playerName string, maxPlayers int) (*models.Ro
|
||||
deck := gm.cl.CreateDeck()
|
||||
gm.cl.Shuffle(deck)
|
||||
|
||||
gm.rooms[roomID] = room
|
||||
gm.players[playerID] = player
|
||||
gm.deck[roomID] = deck
|
||||
gm.discard[roomID] = make([]models.Card, 0)
|
||||
gm.saveRoom(ctx, room)
|
||||
gm.saveDeck(ctx, roomID, deck)
|
||||
gm.saveDiscard(ctx, roomID, []models.Card{})
|
||||
|
||||
return room, player
|
||||
}
|
||||
@@ -274,8 +292,9 @@ func (gm *GameManager) JoinRoom(roomID, playerName string) (*models.Room, *model
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
room, ok := gm.rooms[roomID]
|
||||
if !ok {
|
||||
ctx := context.Background()
|
||||
room, err := gm.loadRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
return nil, nil, ErrRoomNotFound
|
||||
}
|
||||
if len(room.Players) >= room.MaxPlayers {
|
||||
@@ -288,8 +307,8 @@ func (gm *GameManager) JoinRoom(roomID, playerName string) (*models.Room, *model
|
||||
playerID := generateID()
|
||||
player := &models.Player{ID: playerID, Name: playerName, IsOnline: true}
|
||||
room.Players = append(room.Players, player)
|
||||
gm.players[playerID] = player
|
||||
|
||||
gm.saveRoom(ctx, room)
|
||||
return room, player, nil
|
||||
}
|
||||
|
||||
@@ -297,19 +316,20 @@ func (gm *GameManager) LeaveRoom(roomID, playerID string) error {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
room, ok := gm.rooms[roomID]
|
||||
if !ok {
|
||||
ctx := context.Background()
|
||||
room, err := gm.loadRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
return ErrRoomNotFound
|
||||
}
|
||||
|
||||
for i, p := range room.Players {
|
||||
if p.ID == playerID {
|
||||
room.Players = append(room.Players[:i], room.Players[i+1:]...)
|
||||
delete(gm.players, playerID)
|
||||
|
||||
if len(room.Players) == 0 {
|
||||
delete(gm.rooms, roomID)
|
||||
delete(gm.deck, roomID)
|
||||
delete(gm.discard, roomID)
|
||||
gm.deleteRoomData(ctx, roomID)
|
||||
} else {
|
||||
gm.saveRoom(ctx, room)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -317,25 +337,68 @@ func (gm *GameManager) LeaveRoom(roomID, playerID string) error {
|
||||
return ErrPlayerNotFound
|
||||
}
|
||||
|
||||
func (gm *GameManager) LeaveRoomWithTTL(roomID, playerID string) error {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
ctx := context.Background()
|
||||
room, err := gm.loadRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
return ErrRoomNotFound
|
||||
}
|
||||
|
||||
for _, p := range room.Players {
|
||||
if p.ID == playerID {
|
||||
p.IsOnline = false
|
||||
gm.saveRoom(ctx, room)
|
||||
gm.rdb.Set(ctx, redis.PlayerTTLKey(playerID), time.Now().Add(redis.PlayerTTL).Unix(), redis.PlayerTTL)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrPlayerNotFound
|
||||
}
|
||||
|
||||
func (gm *GameManager) MarkPlayerOnline(roomID, playerID string) {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
ctx := context.Background()
|
||||
room, err := gm.loadRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range room.Players {
|
||||
if p.ID == playerID {
|
||||
p.IsOnline = true
|
||||
gm.saveRoom(ctx, room)
|
||||
gm.rdb.Delete(ctx, redis.PlayerTTLKey(playerID))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gm *GameManager) SetReady(roomID, playerID string, ready bool) error {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
room, ok := gm.rooms[roomID]
|
||||
if !ok {
|
||||
ctx := context.Background()
|
||||
room, err := gm.loadRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
return ErrRoomNotFound
|
||||
}
|
||||
|
||||
|
||||
if room.State == models.RoomStatePlaying {
|
||||
return ErrGameStarted
|
||||
}
|
||||
|
||||
|
||||
for _, p := range room.Players {
|
||||
if p.ID == playerID {
|
||||
p.IsReady = ready
|
||||
if room.State == models.RoomStateFinished {
|
||||
room.State = models.RoomStateWaiting
|
||||
}
|
||||
gm.saveRoom(ctx, room)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -346,8 +409,9 @@ func (gm *GameManager) StartGame(roomID string) error {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
room, ok := gm.rooms[roomID]
|
||||
if !ok {
|
||||
ctx := context.Background()
|
||||
room, err := gm.loadRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
return ErrRoomNotFound
|
||||
}
|
||||
if len(room.Players) < 2 {
|
||||
@@ -357,6 +421,8 @@ func (gm *GameManager) StartGame(roomID string) error {
|
||||
return ErrGameStarted
|
||||
}
|
||||
|
||||
lastWinner := room.LastWinner
|
||||
|
||||
for _, p := range room.Players {
|
||||
p.Cards = make([]models.Card, 0)
|
||||
p.CardCount = 0
|
||||
@@ -365,29 +431,40 @@ func (gm *GameManager) StartGame(roomID string) error {
|
||||
|
||||
deck := gm.cl.CreateDeck()
|
||||
gm.cl.Shuffle(deck)
|
||||
gm.deck[roomID] = deck
|
||||
gm.discard[roomID] = make([]models.Card, 0)
|
||||
gm.saveDeck(ctx, roomID, deck)
|
||||
gm.saveDiscard(ctx, roomID, []models.Card{})
|
||||
|
||||
room.State = models.RoomStatePlaying
|
||||
room.RoundCount = 1
|
||||
room.CurrentTurn = 0
|
||||
room.LastPlay = nil
|
||||
room.LastWinner = ""
|
||||
|
||||
gm.dealCards(roomID, 5)
|
||||
if lastWinner != "" {
|
||||
for i, p := range room.Players {
|
||||
if p.ID == lastWinner {
|
||||
room.CurrentTurn = i
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
room.CurrentTurn = 0
|
||||
}
|
||||
|
||||
gm.dealCards(room, 5)
|
||||
gm.saveRoom(ctx, room)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gm *GameManager) dealCards(roomID string, count int) {
|
||||
room := gm.rooms[roomID]
|
||||
deck := gm.deck[roomID]
|
||||
func (gm *GameManager) dealCards(room *models.Room, count int) {
|
||||
ctx := context.Background()
|
||||
deck, _ := gm.loadDeck(ctx, room.ID)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
for _, p := range room.Players {
|
||||
if len(deck) == 0 {
|
||||
deck = gm.discard[roomID]
|
||||
deck, _ = gm.loadDiscard(ctx, room.ID)
|
||||
gm.cl.Shuffle(deck)
|
||||
gm.discard[roomID] = make([]models.Card, 0)
|
||||
gm.saveDiscard(ctx, room.ID, []models.Card{})
|
||||
}
|
||||
if len(deck) > 0 {
|
||||
p.Cards = append(p.Cards, deck[0])
|
||||
@@ -395,7 +472,8 @@ func (gm *GameManager) dealCards(roomID string, count int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
gm.deck[roomID] = deck
|
||||
|
||||
gm.saveDeck(ctx, room.ID, deck)
|
||||
|
||||
for _, p := range room.Players {
|
||||
gm.cl.SortCards(p.Cards)
|
||||
@@ -407,8 +485,9 @@ func (gm *GameManager) PlayCards(roomID, playerID string, cards []models.Card) e
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
room, ok := gm.rooms[roomID]
|
||||
if !ok {
|
||||
ctx := context.Background()
|
||||
room, err := gm.loadRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
return ErrRoomNotFound
|
||||
}
|
||||
if room.State != models.RoomStatePlaying {
|
||||
@@ -439,15 +518,20 @@ func (gm *GameManager) PlayCards(roomID, playerID string, cards []models.Card) e
|
||||
Cards: cards,
|
||||
CardType: cardType,
|
||||
}
|
||||
gm.discard[roomID] = append(gm.discard[roomID], cards...)
|
||||
|
||||
discard, _ := gm.loadDiscard(ctx, roomID)
|
||||
discard = append(discard, cards...)
|
||||
gm.saveDiscard(ctx, roomID, discard)
|
||||
|
||||
if len(player.Cards) == 0 {
|
||||
room.LastWinner = playerID
|
||||
room.State = models.RoomStateFinished
|
||||
return nil
|
||||
gm.rdb.Set(ctx, redis.RoomTTLKey(roomID), time.Now().Add(redis.RoomTTL).Unix(), redis.RoomTTL)
|
||||
} else {
|
||||
room.CurrentTurn = (room.CurrentTurn + 1) % len(room.Players)
|
||||
}
|
||||
|
||||
gm.nextTurn(room)
|
||||
gm.saveRoom(ctx, room)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -455,8 +539,9 @@ func (gm *GameManager) Pass(roomID, playerID string) error {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
room, ok := gm.rooms[roomID]
|
||||
if !ok {
|
||||
ctx := context.Background()
|
||||
room, err := gm.loadRoom(ctx, roomID)
|
||||
if err != nil {
|
||||
return ErrRoomNotFound
|
||||
}
|
||||
if room.State != models.RoomStatePlaying {
|
||||
@@ -469,18 +554,67 @@ func (gm *GameManager) Pass(roomID, playerID string) error {
|
||||
return ErrCannotPass
|
||||
}
|
||||
|
||||
gm.nextTurn(room)
|
||||
room.CurrentTurn = (room.CurrentTurn + 1) % len(room.Players)
|
||||
|
||||
// 一轮结束:回到最后出牌者,发牌并让他先出
|
||||
if room.Players[room.CurrentTurn].ID == room.LastPlay.PlayerID {
|
||||
winnerID := room.LastPlay.PlayerID
|
||||
room.LastPlay = nil
|
||||
gm.dealCards(roomID, 1)
|
||||
room.RoundCount++
|
||||
gm.dealCards(room, 1)
|
||||
|
||||
for i, p := range room.Players {
|
||||
if p.ID == winnerID {
|
||||
room.CurrentTurn = i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gm.saveRoom(ctx, room)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gm *GameManager) nextTurn(room *models.Room) {
|
||||
room.CurrentTurn = (room.CurrentTurn + 1) % len(room.Players)
|
||||
func (gm *GameManager) GetRoom(roomID string) (*models.Room, error) {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
return gm.loadRoom(context.Background(), roomID)
|
||||
}
|
||||
|
||||
func (gm *GameManager) GetRoomState(roomID, playerID string) *models.Room {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
|
||||
room, err := gm.loadRoom(context.Background(), roomID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
players := make([]*models.Player, len(room.Players))
|
||||
for i, p := range room.Players {
|
||||
pp := &models.Player{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
IsReady: p.IsReady,
|
||||
IsOnline: p.IsOnline,
|
||||
CardCount: p.CardCount,
|
||||
}
|
||||
if p.ID == playerID {
|
||||
pp.Cards = p.Cards
|
||||
}
|
||||
players[i] = pp
|
||||
}
|
||||
|
||||
return &models.Room{
|
||||
ID: room.ID,
|
||||
Players: players,
|
||||
CurrentTurn: room.CurrentTurn,
|
||||
State: room.State,
|
||||
LastPlay: room.LastPlay,
|
||||
LastWinner: room.LastWinner,
|
||||
RoundCount: room.RoundCount,
|
||||
MaxPlayers: room.MaxPlayers,
|
||||
}
|
||||
}
|
||||
|
||||
func (gm *GameManager) hasCards(player *models.Player, cards []models.Card) bool {
|
||||
@@ -516,48 +650,118 @@ func (gm *GameManager) removeCards(player *models.Player, cards []models.Card) {
|
||||
player.Cards = newCards
|
||||
}
|
||||
|
||||
func (gm *GameManager) GetRoom(roomID string) (*models.Room, error) {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
room, ok := gm.rooms[roomID]
|
||||
if !ok {
|
||||
return nil, ErrRoomNotFound
|
||||
func (gm *GameManager) cleanupLoop() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
gm.cleanupExpired()
|
||||
case <-gm.stopCleanup:
|
||||
return
|
||||
}
|
||||
}
|
||||
return room, nil
|
||||
}
|
||||
|
||||
func (gm *GameManager) GetRoomState(roomID, playerID string) *models.Room {
|
||||
gm.mu.RLock()
|
||||
defer gm.mu.RUnlock()
|
||||
func (gm *GameManager) cleanupExpired() {
|
||||
gm.mu.Lock()
|
||||
defer gm.mu.Unlock()
|
||||
|
||||
room, ok := gm.rooms[roomID]
|
||||
if !ok {
|
||||
ctx := context.Background()
|
||||
|
||||
gm.rdb.ScanKeys(ctx, "room:*:ttl", func(key string) error {
|
||||
var expiryTime int64
|
||||
if err := gm.rdb.Get(ctx, key, &expiryTime); err != nil {
|
||||
return nil
|
||||
}
|
||||
if time.Now().Unix() > expiryTime {
|
||||
roomID := key[len("room:") : len(key)-len(":ttl")]
|
||||
gm.deleteRoomData(ctx, roomID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
players := make([]*models.Player, len(room.Players))
|
||||
for i, p := range room.Players {
|
||||
pp := &models.Player{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
IsReady: p.IsReady,
|
||||
IsOnline: p.IsOnline,
|
||||
CardCount: p.CardCount,
|
||||
gm.rdb.ScanKeys(ctx, "player:*:ttl", func(key string) error {
|
||||
var expiryTime int64
|
||||
if err := gm.rdb.Get(ctx, key, &expiryTime); err != nil {
|
||||
return nil
|
||||
}
|
||||
if p.ID == playerID {
|
||||
pp.Cards = p.Cards
|
||||
if time.Now().Unix() > expiryTime {
|
||||
playerID := key[len("player:") : len(key)-len(":ttl")]
|
||||
gm.rdb.Delete(ctx, redis.PlayerKey(playerID), redis.PlayerTTLKey(playerID))
|
||||
}
|
||||
players[i] = pp
|
||||
}
|
||||
|
||||
return &models.Room{
|
||||
ID: room.ID,
|
||||
Players: players,
|
||||
CurrentTurn: room.CurrentTurn,
|
||||
State: room.State,
|
||||
LastPlay: room.LastPlay,
|
||||
LastWinner: room.LastWinner,
|
||||
RoundCount: room.RoundCount,
|
||||
MaxPlayers: room.MaxPlayers,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (gm *GameManager) Stop() {
|
||||
gm.stopOnce.Do(func() {
|
||||
close(gm.stopCleanup)
|
||||
})
|
||||
}
|
||||
|
||||
func (gm *GameManager) saveRoom(ctx context.Context, room *models.Room) {
|
||||
gm.rdb.Set(ctx, redis.RoomKey(room.ID), room, redis.RoomTTL)
|
||||
}
|
||||
|
||||
func (gm *GameManager) loadRoom(ctx context.Context, roomID string) (*models.Room, error) {
|
||||
var room models.Room
|
||||
if err := gm.rdb.Get(ctx, redis.RoomKey(roomID), &room); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &room, nil
|
||||
}
|
||||
|
||||
func (gm *GameManager) saveDeck(ctx context.Context, roomID string, deck []models.Card) {
|
||||
gm.rdb.Set(ctx, redis.RoomDeckKey(roomID), deck, redis.RoomTTL)
|
||||
}
|
||||
|
||||
func (gm *GameManager) loadDeck(ctx context.Context, roomID string) ([]models.Card, error) {
|
||||
var deck []models.Card
|
||||
if err := gm.rdb.Get(ctx, redis.RoomDeckKey(roomID), &deck); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return deck, nil
|
||||
}
|
||||
|
||||
func (gm *GameManager) saveDiscard(ctx context.Context, roomID string, discard []models.Card) {
|
||||
gm.rdb.Set(ctx, redis.RoomDiscardKey(roomID), discard, redis.RoomTTL)
|
||||
}
|
||||
|
||||
func (gm *GameManager) loadDiscard(ctx context.Context, roomID string) ([]models.Card, error) {
|
||||
var discard []models.Card
|
||||
if err := gm.rdb.Get(ctx, redis.RoomDiscardKey(roomID), &discard); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return discard, nil
|
||||
}
|
||||
|
||||
func (gm *GameManager) deleteRoomData(ctx context.Context, roomID string) {
|
||||
room, err := gm.loadRoom(ctx, roomID)
|
||||
if err == nil {
|
||||
for _, p := range room.Players {
|
||||
gm.rdb.Delete(ctx, redis.PlayerKey(p.ID), redis.PlayerTTLKey(p.ID))
|
||||
}
|
||||
}
|
||||
gm.rdb.Delete(ctx,
|
||||
redis.RoomKey(roomID),
|
||||
redis.RoomDeckKey(roomID),
|
||||
redis.RoomDiscardKey(roomID),
|
||||
redis.RoomTTLKey(roomID),
|
||||
)
|
||||
}
|
||||
|
||||
func generateID() string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
b := make([]byte, 8)
|
||||
for i := range b {
|
||||
b[i] = charset[rand.Intn(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func cardKey(c models.Card) string {
|
||||
data, _ := json.Marshal(c)
|
||||
return string(data)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user