package handler import ( "encoding/json" "fmt" "runtime/debug" "sync" "time" "bet24.com/log" "bet24.com/servers/common" item "bet24.com/servers/micros/item_inventory/proto" cash "bet24.com/servers/micros/money/proto" chip "bet24.com/servers/micros/money/proto" notification "bet24.com/servers/micros/notification/proto" userservices "bet24.com/servers/micros/userservices/proto" vipservice "bet24.com/servers/micros/userservices/proto" ) const ( Item_ID_Lottery = 4 //抽奖券 ) type user_inventory struct { lock *sync.RWMutex userId int ipAddress string userItemList map[int]*item.UserItem endChan chan int } // InventoryAction const ( Inventory_Consume = iota // 使用 0 Inventory_Add // 增加 1 Inventory_Expire // 到期 2 Inventory_Update // 数据有变化 3 Inventory_Sell // 出售 4 ) func newUserInventory(userId int, ipAddress string) *user_inventory { ret := new(user_inventory) ret.lock = &sync.RWMutex{} ret.userItemList = make(map[int]*item.UserItem) ret.userId = userId ret.ipAddress = ipAddress ret.endChan = make(chan int) //TODO: 去数据库取自己的背包道具 ret.loadUserInventory() // 遍历一下是否有时效道具,使用掉 for _, v := range ret.userItemList { sysItem := getItemManager().getItem(v.ItemId) if sysItem == nil { continue } if sysItem.Duration == 0 { continue } if sysItem.ActiveId == 0 || sysItem.ActiveId == v.ItemId { continue } go ret.consume(v.ItemId, 0, 0, 0, 0) } userTicker := time.NewTicker(time.Duration(5) * time.Second) go func(t *time.Ticker) { for { //循环 select { case <-ret.endChan: t.Stop() return case <-t.C: ret.Timer_checkExpire() } } }(userTicker) return ret } func (this *user_inventory) loadUserInventory() { userItems := getUserItemList(this.userId) this.lock.Lock() this.userItemList = userItems /*for _, v := range userItems { // 如果是永久道具,直接使用 if v.Duration == -1 { go this.consume(v.ItemId, 0, 1, 0, 0) } }*/ this.lock.Unlock() } // 析构,停止计时器等 func (this *user_inventory) destructor() { close(this.endChan) } func (this *user_inventory) Timer_checkExpire() { //检查道具是否时效 var toRemove []*item.UserItem this.lock.RLock() for _, u := range this.userItemList { if this.IsExpired(u) { log.Debug("user_inventory.Timer_checkExpire userId=%d ==> %+v", this.userId, u) // 如果是装扮道具,通知一下用户信息那边修改 sysItem := getItemManager().getItem(u.ItemId) if sysItem == nil { log.Debug("user_inventory.Timer_checkExpire itemId[%d] invalid", u.ItemId) toRemove = append(toRemove, u) continue } if sysItem.Type == item.Item_Decoration { userservices.OnDecorationExpired(this.userId, u.ItemId) } //道具时效, 通知客户端 d, _ := json.Marshal(notification.NotificationInventory{ItemIds: []int{u.ItemId}, Action: Inventory_Expire}) go notification.AddNotification(this.userId, notification.Notification_Inventory, string(d)) //删除道具 toRemove = append(toRemove, u) } } this.lock.RUnlock() if len(toRemove) <= 0 { return } this.lock.Lock() for _, v := range toRemove { //TODO:写道具日志 go this.addLog(v.ItemId, v.Count, -v.Count, 0, 0, "道具过期", this.ipAddress) //TODO:通知数据库道具过期 delUserItem(this.userId, v) delete(this.userItemList, v.ItemId) } this.lock.Unlock() } func (this *user_inventory) getItemList() []*item.UserItem { var l []*item.UserItem this.lock.RLock() defer this.lock.RUnlock() for _, v := range this.userItemList { l = append(l, v) } return l } func (this *user_inventory) getItemCount(itemId int) int { this.lock.RLock() defer this.lock.RUnlock() for _, v := range this.userItemList { if v.ItemId == itemId { return v.Count } } return 0 } // 获取抽奖券 func (this *user_inventory) getLottery() *item.UserItem { this.lock.RLock() defer this.lock.RUnlock() for _, v := range this.userItemList { if v.ItemId == Item_ID_Lottery { return v } } return nil } // 消耗抽奖券 func (this *user_inventory) consumeLottery(count int) (bool, string) { return this.consume(Item_ID_Lottery, 0, count, 0, 0) } func (this *user_inventory) consumeBulk(items []item.ItemPack, logType int) bool { var rollBackItems []item.ItemPack allOk := true for _, v := range items { ok, _ := this.consume(v.ItemId, 0, v.Count, logType, 0) if ok { rollBackItems = append(rollBackItems, v) } else { allOk = false break } } if !allOk { for _, v := range rollBackItems { this.addItem(v, logType) } } return allOk } func (this *user_inventory) consume(itemId int, bullet int, count int, logType int, isGift int) (bool, string) { // log.Debug("user_inventory.consume userId=%d itemId=%d bullet=%d count=%d logType=%d isGift=%d", // this.userId, itemId, bullet, count, logType, isGift) sysItem := getItemManager().getItem(itemId) if sysItem == nil { log.Debug("user_inventory.consume itemId[%d] invalid", itemId) return false, "无效的道具ID" } // 非背包展示道具,直接调数据库接口更新 if sysItem.Type == item.Item_Gold || sysItem.Type == item.Item_Chip || sysItem.Type == item.Item_Vitality { // TODO: 通知数据库刷新 ok := false errMsg := "" switch sysItem.Type { case item.Item_Gold: ok = cash.ReduceMoney(this.userId, count, logType, "道具模块", "扣金币", this.ipAddress) if !ok { errMsg = "金币不足" } case item.Item_Chip: ok = chip.ReduceChip(this.userId, count, logType, "道具模块", "扣筹码", this.ipAddress) == 1 if !ok { errMsg = "筹码不足" } } return ok, errMsg } if count == 0 { count = 1 } this.lock.Lock() defer this.lock.Unlock() // 我有没有这个道具? myItem, ok := this.userItemList[itemId] if !ok { log.Debug("user_inventory.consume itemId[%d] not exist", itemId) return false, "道具不存在" } if myItem.Start > 0 && !this.IsExpired(myItem) { // 本身是已使用的道具,这里只是切换成激活状态 return true, "使用成功" } if myItem.Count < count { log.Debug("user_inventory.consume itemId[%d] not enough %d < %d", itemId, myItem.Count, count) return false, "道具数量不够" } //时效道具, 加时间 retMsg := "使用成功" isDuration := false if sysItem.ActiveId > 0 && isGift == 0 { isDuration = true if l, ok := this.userItemList[sysItem.ActiveId]; ok { // 如果本身为永久道具,则折现 if l.Duration == -1 { gold := sysItem.Value * count go this.addItem(item.ItemPack{ItemId: item.Item_Gold, Count: gold}, common.LOGTYPE_TOOL_CONVERT) retMsg = fmt.Sprintf("道具已折算成[%d]金币", gold) } else if myItem.Duration == -1 { l.Duration = -1 } else { // 根据个数确定时长 l.Duration += myItem.Duration * count } } else { //新增使用中的时效道具 ui := &item.UserItem{ ItemId: sysItem.ActiveId, Count: 1, Start: common.GetTimeStamp(), Duration: myItem.Duration * count, } this.userItemList[sysItem.ActiveId] = ui } go updateUserItem(this.userId, this.userItemList[sysItem.ActiveId]) } // 如果是兑换券 if sysItem.Type == item.Item_GiftCard { ok, retMsg = getGiftCardManager().useCard(this.userId, sysItem.Id) // log.Debug("user_inventory.consume.useCard userId=%d itemId=%d ret=%v retMsg=%s", this.userId, sysItem.Id, ok, retMsg) if !ok { return ok, retMsg } } currCount := myItem.Count myItem.Count -= count remark := "使用道具" + common.GetLogTypeName(bullet) go this.addLog(itemId, currCount, -count, myItem.Count, 0, remark, this.ipAddress) //道具用完了, 删除 if myItem.Count == 0 { //TODO:通知数据库道具删除 go delUserItem(this.userId, myItem) delete(this.userItemList, itemId) } else { //TODO:通知数据库道具消耗 go updateUserItem(this.userId, myItem) } //通知客户端 if !isDuration { d, _ := json.Marshal(notification.NotificationInventory{ItemIds: []int{itemId}, Action: Inventory_Consume}) go notification.AddNotification(this.userId, notification.Notification_Inventory, string(d)) } return true, retMsg } func (this *user_inventory) sell(itemId, count, logType int) (bool, string) { log.Debug("user_inventory.sell userId=%d itemId=%d count=%d logType=%d", this.userId, itemId, count, logType) sysItem := getItemManager().getItem(itemId) if sysItem == nil { log.Debug("user_inventory.sell itemId[%d] invalid", itemId) return false, "无效的道具ID" } if sysItem.Value <= 0 { log.Debug("user_inventory.sell itemId[%d] is not sell", itemId) return false, "该道具无法出售" } if sysItem.ShowPrice <= 0 { return false, "该道具无法出售" } if logType <= 0 { logType = common.LOGTYPE_TOOL_SELL } if count == 0 { count = 1 } this.lock.Lock() defer this.lock.Unlock() // 我有没有这个道具? myItem, ok := this.userItemList[itemId] if !ok { log.Debug("user_inventory.sell itemId[%d] not exist", itemId) return false, "道具不存在" } if myItem.Count <= 0 { log.Debug("user_inventory.sell itemId[%d] is zero", itemId) return false, "道具不存在" } if myItem.Count < count { return false, "道具数量不够" } currCount := myItem.Count myItem.Count -= count gold := sysItem.Value * count go this.addItem(item.ItemPack{ItemId: item.Item_Gold, Count: gold}, logType) retMsg := fmt.Sprintf("道具已折算成[%d]金币", gold) go this.addLog(itemId, currCount, -count, myItem.Count, logType, "扣减", this.ipAddress) // 道具用完了, 删除 if myItem.Count == 0 { // TODO:通知数据库道具删除 go delUserItem(this.userId, myItem) delete(this.userItemList, itemId) } else { // TODO:通知数据库道具消耗 go updateUserItem(this.userId, myItem) } // 通知客户端 d, _ := json.Marshal(notification.NotificationInventory{ItemIds: []int{itemId}, Action: Inventory_Sell}) go notification.AddNotification(this.userId, notification.Notification_Inventory, string(d)) return true, retMsg } func (this *user_inventory) addLog(itemId, currCount, wantCount, stillCount, logType int, remark, ipAddress string) { // log.Debug("user_inventory addLog %d:%d currCount=%d wantCount=%d stillCount=%d logType=%d remark=%s", // this.userId, itemId, currCount, wantCount, stillCount, logType, remark) remark = common.GetLogTypeName(logType) + remark go addLog(this.userId, itemId, currCount, wantCount, stillCount, remark, ipAddress) } func (this *user_inventory) addItemsWithExpireTime(items []item.ItemPack, logType int, expireTime int) bool { // 特殊接口,只处理时效道具 now := int(time.Now().Unix()) if expireTime < now { log.Error("user_inventory.addItemsWithExpireTime userId[%d] expireTime[%d] < now[%d]", this.userId, expireTime, now) return false } for _, v := range items { sysItem := getItemManager().getItem(v.ItemId) if sysItem == nil { log.Error("user_inventory.addItemsWithExpireTime invalid userId[%d] ItemId %d", this.userId, v.ItemId) continue } itemId := v.ItemId if sysItem.ActiveId != 0 { itemId = sysItem.ActiveId } // 我本身有没有 this.lock.Lock() myItem, ok := this.userItemList[itemId] if ok { myItem.Start = now myItem.Duration = expireTime - now } else { this.userItemList[itemId] = &item.UserItem{ItemId: itemId, Start: now, Duration: expireTime - now, Count: 1} myItem = this.userItemList[itemId] } this.lock.Unlock() //TODO: 写道具日志 go this.addLog(myItem.ItemId, 0, v.Count, myItem.Count, logType, "添加道具", this.ipAddress) //TODO: 通知数据库更新道具信息 go updateUserItem(this.userId, myItem) } return true } func (this *user_inventory) addItem(it item.ItemPack, logType int) bool { // log.Debug("user_inventory.addItem userId=%d ipAddress=%s it=%+v", this.userId, this.ipAddress, it) if it.Count <= 0 { log.Debug("user_inventory.addItem userId[%d] %v", this.userId, it) log.Debug("%s", debug.Stack()) return false } s := getItemManager().getItem(it.ItemId) if s == nil { log.Error("user_inventory.addItem invalid userId[%d] ItemId %d", this.userId, it.ItemId) return false } //log.Debug("user_inventory.addItem UserId[%d] sys[%+v]", this.userId, s) // 非背包展示道具,直接调数据库接口更新 if s.Type == item.Item_Gold || s.Type == item.Item_Chip || s.Type == item.Item_Vitality || s.Type == item.Item_Vip { // TODO: 通知数据库刷新 switch s.Type { case item.Item_Gold: cash.GiveMoney(this.userId, it.Count, logType, "道具模块", "加金币", this.ipAddress) case item.Item_Chip: chip.GiveChip(this.userId, it.Count, logType, "道具模块", "加筹码", this.ipAddress) case item.Item_Vip: // 添加vip时长 log.Debug("user_inventory.addItem UserId[%d] AddVipSeconds %d", this.userId, s.Duration) vipservice.AddVipSeconds(this.userId, s.Duration) } return true } else if s.Type == item.Item_Addminutes || s.Type == item.Item_Physical || s.Type == item.Item_Cash { //实物 return true } // 如果是虚拟道具(限时道具本体),不添加 if getItemManager().isVirtualItem(it.ItemId) { log.Release("user_inventory.addItem virtual item [%d]", it.ItemId) return false } this.lock.Lock() myItem, ok := this.userItemList[it.ItemId] if ok { currCount := 0 currCount = myItem.Count myItem.Count += it.Count this.lock.Unlock() //TODO: 写道具日志 go this.addLog(myItem.ItemId, currCount, it.Count, myItem.Count, logType, "添加道具", this.ipAddress) // 如果是兑换券 if s.Type == item.Item_GiftCard { go getGiftCardManager().genCard(this.userId, s.Id, it.Count) } //TODO: 通知数据库更新道具信息 updateUserItem(this.userId, myItem) return true } this.lock.Unlock() // 如果是时效道具,直接使用 if s.Duration != 0 { this.lock.Lock() this.userItemList[it.ItemId] = &item.UserItem{ItemId: s.Id, Start: 0, Duration: s.Duration, Count: it.Count} this.lock.Unlock() //TODO: 写道具日志 this.addLog(it.ItemId, 0, it.Count, 0, logType, "新增道具", this.ipAddress) this.consume(it.ItemId, 0, it.Count, 0, 0) return true } //玩家还没有该道具,新增 ui := &item.UserItem{ ItemId: s.Id, Start: 0, Duration: s.Duration, } ui.Count = it.Count this.lock.Lock() this.userItemList[ui.ItemId] = ui this.lock.Unlock() //TODO: 写道具日志 this.addLog(ui.ItemId, 0, it.Count, ui.Count, logType, "新增道具", this.ipAddress) // 如果是兑换券 if s.Type == item.Item_GiftCard { go getGiftCardManager().genCard(this.userId, s.Id, it.Count) } //TODO: 通知数据库更新道具信息 go updateUserItem(this.userId, ui) return true } func (this *user_inventory) IsExpired(u *item.UserItem) bool { if u.Duration <= 0 { return false } if u.Start == 0 || u.Duration == 0 { return false } now := common.GetTimeStamp() return (now - u.Start) > u.Duration } func (this *user_inventory) reduceItemByAdmin(opUserId int, opUserName string, itemId, count int) (bool, string) { if count <= 0 { return false, "扣减失败,无效参数" } sysItem := getItemManager().getItem(itemId) if sysItem == nil { log.Debug("user_inventory.reduceItemByAdmin itemId[%d] invalid", itemId) return false, "扣减失败,无效的道具ID" } this.lock.Lock() defer this.lock.Unlock() // 我有没有这个道具? myItem, ok := this.userItemList[itemId] if !ok { log.Debug("user_inventory.reduceItemByAdmin itemId[%d] not exist", itemId) return false, "扣减失败,道具不存在" } if myItem.Count <= 0 { log.Debug("user_inventory.reduceItemByAdmin itemId[%d] is zero", itemId) return false, "扣减失败,道具不存在" } currCount := myItem.Count myItem.Count -= count msg := fmt.Sprintf("后台扣减道具(opUserId:%d opUserName:%s)", opUserId, opUserName) go this.addLog(itemId, currCount, -count, myItem.Count, 0, msg, this.ipAddress) //道具用完了, 删除 if myItem.Count == 0 { //TODO:通知数据库道具删除 delUserItem(this.userId, myItem) delete(this.userItemList, itemId) } else { //TODO:通知数据库道具消耗 updateUserItem(this.userId, myItem) } //通知客户端 d, _ := json.Marshal(notification.NotificationInventory{ItemIds: []int{itemId}, Action: Inventory_Consume}) go notification.AddNotification(this.userId, notification.Notification_Inventory, string(d)) retmsg := fmt.Sprintf("扣减成功,道具id=%d 道具名称=%s 扣减道具数=%d 剩余道具数=%d", myItem.ItemId, sysItem.Name, count, myItem.Count) return true, retmsg }