|
@@ -0,0 +1,329 @@
|
|
|
+package bagdb
|
|
|
+
|
|
|
+import (
|
|
|
+ "database/sql"
|
|
|
+ "encoding/json"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "log"
|
|
|
+ mysqlmgr "server/db/mysql"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ _ "github.com/go-sql-driver/mysql"
|
|
|
+)
|
|
|
+
|
|
|
+// Item 背包中的道具
|
|
|
+type Item struct {
|
|
|
+ ID int `json:"item_id"`
|
|
|
+ Quantity int `json:"quantity"`
|
|
|
+}
|
|
|
+
|
|
|
+// InventoryData 背包数据结构
|
|
|
+type InventoryData struct {
|
|
|
+ Slots []Item `json:"slots"`
|
|
|
+}
|
|
|
+
|
|
|
+// ItemInfo 道具基础信息
|
|
|
+type ItemInfo struct {
|
|
|
+ ID int `json:"item_id"`
|
|
|
+ Name string `json:"item_name"`
|
|
|
+ MaxStack int `json:"max_stack"` // 0表示不可叠加
|
|
|
+}
|
|
|
+
|
|
|
+// ItemRequest 添加道具请求
|
|
|
+type ItemRequest struct {
|
|
|
+ ItemID int `json:"item_id"`
|
|
|
+ Quantity int `json:"quantity"`
|
|
|
+}
|
|
|
+
|
|
|
+// GetUserInventory 获取用户背包
|
|
|
+func GetUserInventory(userID string) (*InventoryData, error) {
|
|
|
+ var dataStr string
|
|
|
+ err := mysqlmgr.QueryRow("SELECT inventory_data FROM user_inventory WHERE user_id = ?", userID).Scan(&dataStr)
|
|
|
+ if err != nil {
|
|
|
+ if errors.Is(err, sql.ErrNoRows) {
|
|
|
+ return &InventoryData{Slots: make([]Item, 0)}, nil
|
|
|
+ }
|
|
|
+ return nil, fmt.Errorf("查询用户背包失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ var data InventoryData
|
|
|
+ if err = json.Unmarshal([]byte(dataStr), &data); err != nil {
|
|
|
+ return nil, fmt.Errorf("解析背包数据失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return &data, nil
|
|
|
+}
|
|
|
+
|
|
|
+// SaveUserInventory 保存用户背包
|
|
|
+func SaveUserInventory(userID string, data *InventoryData) error {
|
|
|
+ jsonData, err := json.Marshal(data)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("序列化背包数据失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ _, err = mysqlmgr.Insert(`
|
|
|
+ INSERT INTO user_inventory (user_id, inventory_data, version)
|
|
|
+ VALUES (?, ?, 1)
|
|
|
+ ON DUPLICATE KEY UPDATE
|
|
|
+ inventory_data = VALUES(inventory_data),
|
|
|
+ version = version + 1`,
|
|
|
+ userID, string(jsonData))
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("保存背包数据失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// GetItemInfo 获取道具信息 ,::后面改成从 redis 查询 ,实现逻辑是 启动服务器时,把所有道具添加到redis库里
|
|
|
+func GetItemInfo(itemID int) (*ItemInfo, error) {
|
|
|
+ var item ItemInfo
|
|
|
+ err := mysqlmgr.QueryRow(`
|
|
|
+ SELECT item_id, item_name, max_stack
|
|
|
+ FROM items
|
|
|
+ WHERE item_id = ?`, itemID).
|
|
|
+ Scan(&item.ID, &item.Name, &item.MaxStack)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("查询道具信息失败: %v", err)
|
|
|
+ }
|
|
|
+ return &item, nil
|
|
|
+}
|
|
|
+
|
|
|
+// AddItemsToMail 添加道具到邮箱
|
|
|
+func AddItemsToMail(userID string, items []ItemRequest) error {
|
|
|
+ // 开始事务
|
|
|
+ tx, err := mysqlmgr.Begin()
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("开始事务失败: %v", err)
|
|
|
+ }
|
|
|
+ defer tx.Rollback()
|
|
|
+
|
|
|
+ // 为每组道具创建一封邮件
|
|
|
+ for _, itemReq := range items {
|
|
|
+ itemInfo, err := GetItemInfo(itemReq.ItemID)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ _, err = tx.Exec(`
|
|
|
+ INSERT INTO user_mail
|
|
|
+ (user_id, item_id, quantity, title, content, expire_time, status)
|
|
|
+ VALUES (?, ?, ?, ?, ?, ?, 0)`,
|
|
|
+ userID,
|
|
|
+ itemReq.ItemID,
|
|
|
+ itemReq.Quantity,
|
|
|
+ fmt.Sprintf("获得%d个%s", itemReq.Quantity, itemInfo.Name),
|
|
|
+ fmt.Sprintf("由于背包空间不足,系统将%d个%s发送到您的邮箱", itemReq.Quantity, itemInfo.Name),
|
|
|
+ time.Now().Add(30*24*time.Hour),
|
|
|
+ )
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("添加到邮箱失败: %v", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return tx.Commit()
|
|
|
+}
|
|
|
+
|
|
|
+// AcquireMultipleItems 获取多个道具
|
|
|
+func AcquireMultipleItems(userID string, itemRequests []ItemRequest) ([]ItemRequest, error) {
|
|
|
+ // 1. 获取用户最大格子数
|
|
|
+ var maxSlots int
|
|
|
+ err := mysqlmgr.QueryRow("SELECT max_slots FROM users WHERE user_id = ?", userID).Scan(&maxSlots)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("查询用户信息失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取当前背包数据
|
|
|
+ inventory, err := GetUserInventory(userID)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 计算剩余空间
|
|
|
+ remainingSlots := maxSlots - len(inventory.Slots)
|
|
|
+ if remainingSlots < 0 {
|
|
|
+ remainingSlots = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 处理每种道具
|
|
|
+ var mailItems []ItemRequest
|
|
|
+ updatedInventory := &InventoryData{Slots: make([]Item, len(inventory.Slots))}
|
|
|
+ copy(updatedInventory.Slots, inventory.Slots)
|
|
|
+
|
|
|
+ for _, req := range itemRequests {
|
|
|
+ itemInfo, err := GetItemInfo(req.ItemID)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ remainingQuantity := req.Quantity
|
|
|
+
|
|
|
+ // 可叠加道具处理
|
|
|
+ if itemInfo.MaxStack > 1 {
|
|
|
+ // 先尝试堆叠到已有格子
|
|
|
+ for i := range updatedInventory.Slots {
|
|
|
+ if updatedInventory.Slots[i].ID == req.ItemID &&
|
|
|
+ updatedInventory.Slots[i].Quantity < itemInfo.MaxStack {
|
|
|
+ canAdd := itemInfo.MaxStack - updatedInventory.Slots[i].Quantity
|
|
|
+ add := min(remainingQuantity, canAdd)
|
|
|
+ updatedInventory.Slots[i].Quantity += add
|
|
|
+ remainingQuantity -= add
|
|
|
+ if remainingQuantity == 0 {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理剩余数量
|
|
|
+ for remainingQuantity > 0 {
|
|
|
+ if remainingSlots <= 0 {
|
|
|
+ // 没有空格子了,剩余道具发到邮箱
|
|
|
+ mailItems = append(mailItems, ItemRequest{
|
|
|
+ ItemID: req.ItemID,
|
|
|
+ Quantity: remainingQuantity,
|
|
|
+ })
|
|
|
+ remainingQuantity = 0
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ // 不可叠加道具或可叠加道具的新格子
|
|
|
+ if itemInfo.MaxStack <= 1 {
|
|
|
+ // 不可叠加道具,每个占一个格子
|
|
|
+ updatedInventory.Slots = append(updatedInventory.Slots, Item{
|
|
|
+ ID: req.ItemID,
|
|
|
+ Quantity: 1,
|
|
|
+ })
|
|
|
+ remainingQuantity--
|
|
|
+ remainingSlots--
|
|
|
+ } else {
|
|
|
+ // 可叠加道具,创建新格子
|
|
|
+ add := min(remainingQuantity, itemInfo.MaxStack)
|
|
|
+ updatedInventory.Slots = append(updatedInventory.Slots, Item{
|
|
|
+ ID: req.ItemID,
|
|
|
+ Quantity: add,
|
|
|
+ })
|
|
|
+ remainingQuantity -= add
|
|
|
+ remainingSlots--
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 保存背包数据
|
|
|
+ if len(updatedInventory.Slots) != len(inventory.Slots) ||
|
|
|
+ len(mailItems) == 0 {
|
|
|
+ if err := SaveUserInventory(userID, updatedInventory); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 如果有道具需要发到邮箱
|
|
|
+ if len(mailItems) > 0 {
|
|
|
+ if err := AddItemsToMail(userID, mailItems); err != nil {
|
|
|
+ return mailItems, fmt.Errorf("部分道具添加到背包,但发送到邮箱失败: %v", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return mailItems, nil
|
|
|
+}
|
|
|
+
|
|
|
+// ConsumeMultipleItems 消耗多个道具
|
|
|
+func ConsumeMultipleItems(userID string, itemRequests []ItemRequest) error {
|
|
|
+ // 1. 获取当前背包数据
|
|
|
+ inventory, err := GetUserInventory(userID)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 检查道具数量是否足够
|
|
|
+ itemCounts := make(map[int]int)
|
|
|
+ for _, req := range itemRequests {
|
|
|
+ itemCounts[req.ItemID] += req.Quantity
|
|
|
+ }
|
|
|
+
|
|
|
+ // 统计当前拥有的道具数量
|
|
|
+ currentCounts := make(map[int]int)
|
|
|
+ for _, slot := range inventory.Slots {
|
|
|
+ currentCounts[slot.ID] += slot.Quantity
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查是否足够
|
|
|
+ for itemID, needed := range itemCounts {
|
|
|
+ if currentCounts[itemID] < needed {
|
|
|
+ itemInfo, _ := GetItemInfo(itemID)
|
|
|
+ return fmt.Errorf("道具数量不足,需要%d个%s但只有%d个",
|
|
|
+ needed, itemInfo.Name, currentCounts[itemID])
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 扣除道具
|
|
|
+ newSlots := make([]Item, 0, len(inventory.Slots))
|
|
|
+ remainingToConsume := make(map[int]int)
|
|
|
+ for itemID, count := range itemCounts {
|
|
|
+ remainingToConsume[itemID] = count
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, slot := range inventory.Slots {
|
|
|
+ if consume, ok := remainingToConsume[slot.ID]; ok && consume > 0 {
|
|
|
+ if slot.Quantity > consume {
|
|
|
+ // 这个格子有剩余
|
|
|
+ newSlots = append(newSlots, Item{
|
|
|
+ ID: slot.ID,
|
|
|
+ Quantity: slot.Quantity - consume,
|
|
|
+ })
|
|
|
+ remainingToConsume[slot.ID] = 0
|
|
|
+ } else {
|
|
|
+ // 完全消耗这个格子的道具
|
|
|
+ remainingToConsume[slot.ID] -= slot.Quantity
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 不需要消耗这个格子的道具
|
|
|
+ newSlots = append(newSlots, slot)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 保存背包数据
|
|
|
+ updatedInventory := &InventoryData{Slots: newSlots}
|
|
|
+ return SaveUserInventory(userID, updatedInventory)
|
|
|
+}
|
|
|
+
|
|
|
+func min(a, b int) int {
|
|
|
+ if a < b {
|
|
|
+ return a
|
|
|
+ }
|
|
|
+ return b
|
|
|
+}
|
|
|
+
|
|
|
+func example() {
|
|
|
+
|
|
|
+ // 示例1: 同时获取多种道具
|
|
|
+ userID := "user123"
|
|
|
+ itemsToAdd := []ItemRequest{
|
|
|
+ {ItemID: 101, Quantity: 15}, // 可叠加道具
|
|
|
+ {ItemID: 201, Quantity: 3}, // 不可叠加道具
|
|
|
+ {ItemID: 301, Quantity: 5}, // 其他道具
|
|
|
+ }
|
|
|
+
|
|
|
+ mailItems, err := AcquireMultipleItems(userID, itemsToAdd)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("获取道具失败: %v", err)
|
|
|
+ } else if len(mailItems) > 0 {
|
|
|
+ log.Printf("部分道具已发送到邮箱: %+v", mailItems)
|
|
|
+ } else {
|
|
|
+ log.Println("所有道具已成功添加到背包")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 示例2: 同时消耗多种道具
|
|
|
+ itemsToConsume := []ItemRequest{
|
|
|
+ {ItemID: 101, Quantity: 5},
|
|
|
+ {ItemID: 201, Quantity: 1},
|
|
|
+ }
|
|
|
+
|
|
|
+ err = ConsumeMultipleItems(userID, itemsToConsume)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("消耗道具失败: %v", err)
|
|
|
+ } else {
|
|
|
+ log.Println("道具消耗成功")
|
|
|
+ }
|
|
|
+}
|