package call import ( "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "runtime" "server/common" "server/db" "server/natsClient" "server/pb" "server/util" "time" "github.com/liangdas/mqant/log" "gorm.io/gorm" ) var ( ErrNotEnoughBalance = errors.New("not enough balance") ) const ( ProTypeSettle = iota ProTypeMineCash // 扣除可退出余额,判断余额是否足够 ProTypeAll ) // BaseCurrency 更新玩家字段的基本对象 type BaseCurrency struct { subCurrency subCurrency tx *gorm.DB *common.CurrencyBalance notNotify bool // 是否需要通知客户端 notifyChan chan *ProRes ProType int // IsMine bool // 是否判断玩家余额是否充足 // IsRecharge bool // 是否更新充值账户 } // NewCurrency 创建一个更新玩家字段对象 func NewCurrency(data *common.UpdateCurrency) *BaseCurrency { b := new(BaseCurrency) if data.CurrencyBalance.Type <= 0 || data.CurrencyBalance.Type >= common.CurrencyAll { return nil } if data.Value == 0 { return nil } b.notNotify = data.NotNotify b.tx = data.Tx b.CurrencyBalance = data.CurrencyBalance b.Time = time.Now().Unix() if b.ChannelID == 0 { playerInfo, _ := GetUserXInfo(data.UID, "channel_id") b.ChannelID = playerInfo.ChannelID } log.Debug("new update:%+v", *b.CurrencyBalance) return b } // UpdateCurrency 更新玩家数据并通知客户端 func UpdateCurrency(data *common.UpdateCurrency, tx *gorm.DB) error { b := NewCurrency(data) if b == nil { return errors.New("invalid update") } b.tx = tx err := b.UpdateCurrency() if err != nil { return err } b.Notify() return nil } func (b *BaseCurrency) Notify() { if err := Publish(natsClient.TopicInnerRefreshGold, &pb.InnerRefreshGold{UID: uint32(b.UID), Pair: []*pb.CurrencyPair{{Type: int64(b.Type), Value: b.Value}}, Event: uint32(b.Event)}); err != nil { log.Error("err:%v", err) } if b.notNotify { return } var resp pb.PlayerBalanceResp resp.Type = int64(b.Type) resp.Balance = b.Balance resp.Value = b.Value resp.Event = int64(b.Event) if caller != nil { SendNR(b.UID, int(pb.ServerCommonResp_CommonPlayerBalanceResp), &resp, "common") } } func Notify(uid int, bal, changeVal int64, t common.CurrencyType, event common.CurrencyEvent, exs1, exs2 string) { var resp pb.PlayerBalanceResp resp.Balance = bal resp.Type = int64(t) resp.Event = int64(event) resp.Exs1 = exs1 resp.Exs2 = exs2 resp.Value = changeVal if caller != nil { SendNR(uid, int(pb.ServerCommonResp_CommonPlayerBalanceResp), &resp, "common") } } // ProRes 存储过程返回结构 type ProRes struct { Result int Type common.CurrencyType Balance int64 // 余额 Err error } // 存储过程修改金币 func UpdateCurrencyPro(data *common.UpdateCurrency) (*ProRes, error) { b := NewCurrency(data) if b == nil { return nil, errors.New("invalid update") } b.notifyChan = make(chan *ProRes, 1) b.UpdateCurrencyPro() // retPro := new(ProRes) // for pro := range b.notifyChan { // if pro.Err != nil { // return pro, pro.Err // } // retPro = pro // break // } retPro := <-b.notifyChan b.Notify() return retPro, nil } // 存储过程修改金币,立即返回,失败后不重试(不判断玩家余额是否充足,可能扣成负数) func UpdateCurrencyProReal(data *common.UpdateCurrency) *ProRes { pro := &ProRes{} b := NewCurrency(data) if b == nil { pro.Err = errors.New("invalid update") return pro } pro, err := b.UpdatePro() if err != nil { pro.Err = err return pro } if pro.Err != nil { return pro } b.Notify() return pro } // 存储过程修改金币,立即返回,失败后不重试(会判断玩家余额是否充足) func MineCurrencyProReal(data *common.UpdateCurrency) *ProRes { pro := &ProRes{} b := NewCurrency(data) if b == nil { pro.Err = errors.New("invalid update") return pro } b.ProType = ProTypeMineCash pro, err := b.UpdatePro() if err != nil { pro.Err = err return pro } if pro.Err != nil { return pro } if pro.Result == 1 { pro.Err = ErrNotEnoughBalance return pro } b.Notify() return pro } // 通过存储过程改变金币(不判断玩家余额是否充足,可能扣成负数) func (b *BaseCurrency) UpdateCurrencyPro() { util.IndexTryCallback(func() error { pro, err := b.UpdatePro() if err != nil { return err } b.notifyChan <- pro return nil }, func() { b.notifyChan <- &ProRes{Err: fmt.Errorf("index try fail")} }) } func (b *BaseCurrency) UpdatePro() (*ProRes, error) { uid := b.UID field := b.Type.GetCurrencyName() pro := &ProRes{Type: b.Type} callName := "settleCurrency" if b.ProType == ProTypeMineCash { callName = "mineCurrency" } err := db.Mysql().C().Raw(fmt.Sprintf("call %s(%d,'%v',%d,%d,@a,@b)", callName, uid, field, b.Value, b.NeedBet)).Scan(pro).Error if err != nil { log.Error("err:%v", err) return pro, err } b.Balance = pro.Balance util.Go(func() { WriteBalance(b.CurrencyBalance) }) return pro, nil } func (b *BaseCurrency) UpdateCurrency() (err error) { tx := b.tx if tx == nil { tx = db.Mysql().C() } defer func() { if r := recover(); r != nil { buf := make([]byte, 1024) runtime.Stack(buf, false) log.Error(string(buf)) return } }() uid := b.UID field := b.Type.GetCurrencyName() log.Debug("player %v update currency:%+v", uid, *b) pc := &common.PlayerCurrency{UID: uid} tableName := pc.TableName() err = db.Mysql().C().Table(tableName).Where(&common.PlayerCurrency{UID: uid}).Select(field).Scan(&b.Balance).Error if err != nil { return } if b.Value > 0 { err = tx.Model(&common.PlayerCurrency{UID: uid}).Updates(map[string]interface{}{field: gorm.Expr(fmt.Sprintf("%s + ?", field), b.Value)}).Error if err != nil { return } } else { if b.Balance < -b.Value { err = errors.New("not enough balance") return } res := tx.Table(tableName).Where(&common.PlayerCurrency{UID: uid}).Where(fmt.Sprintf("uid = %d and %s>=%d", uid, field, -b.Value)). Updates(map[string]interface{}{field: gorm.Expr(fmt.Sprintf("%s + ?", field), -b.Value)}) if res.RowsAffected == 0 || res.Error != nil { log.Error("err:%e", res.Error) err = errors.New("not enough balance") return } } if b.NeedBet > 0 { err = tx.Model(&common.PlayerProfile{}).Where("uid = ?", uid).Updates(map[string]interface{}{"need_bet": gorm.Expr("need_bet + ?", b.NeedBet)}).Error if err != nil { log.Error("err:%v", err) return } } b.Balance += b.Value err = WriteBalance(b.CurrencyBalance, tx) if err != nil { log.Error("err:%v", err) return } if b.subCurrency != nil { err = b.subCurrency.updateCurrency() if err != nil { log.Error("err:%v", err) } return } return } type subCurrency interface { updateCurrency() error } func WriteBalance(b *common.CurrencyBalance, d ...*gorm.DB) error { tx := db.Mysql().C() if d != nil { tx = d[0] } if len(b.Exs1) > 64 { b.Exs1 = b.Exs1[:64] } if len(b.Exs2) > 64 { b.Exs2 = b.Exs2[:64] } if len(b.Exs3) > 64 { b.Exs3 = b.Exs3[:64] } err := tx.Table(b.TableName()).Create(b).Error if err != nil { log.Error("dbUpdateCurrency AddCurrencyBalance error:%v", err) return err } db.ES().InsertToESGO(common.ESIndexBalance, b) return nil } // 汇率转换(转成美元) func Rate(t common.CurrencyType, amount int64) int64 { switch t { case common.CurrencyBrazil: // 汇率每天刷新一次 rate := GetConfigCurrencyRateUSD(t) if rate == 0 { rate = 2000 } return amount * rate / 10000 default: return amount } } func RateBRL(t common.CurrencyType, amount int64) int64 { switch t { case common.CurrencyBrazil: return amount case common.CurrencyUSDT: // 汇率每天刷新一次 rate := GetConfigCurrencyRateUSD(common.CurrencyBrazil) if rate == 0 { rate = 2000 } return amount * 10000 / rate default: return amount } } const ( RateKey = "0P0U7HP7G0GIDJJ6" // 汇率查询网站apikey ) type FXRate struct { FromCurrencyCode string `json:"1. From_Currency Code"` FromCurrencyName string `json:"2. From_Currency Name"` ToCurrencyCode string `json:"3. To_Currency Code"` ToCurrencyName string `json:"4. To_Currency Name"` ExchangeRate float64 `json:"5. Exchange Rate,string"` } // GetRateFromAPI 查询货币汇率,返回万分位 func GetRateFromAPI(t common.CurrencyType) int64 { if t == common.CurrencyUSDT { return 10000 } url := fmt.Sprintf("https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE&from_currency=%v&to_currency=USD&apikey=%v", t.GetCurrencyName(), RateKey) resp, err := http.Get(url) if err != nil { log.Error("err:%v", err) return 0 } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Error("err:%v", err) return 0 } log.Debug("GetRateFromAPI:%v", string(body)) var data map[string]FXRate err = json.Unmarshal(body, &data) if err != nil { log.Error("err:%v", err) return 0 } fxRate, ok := data["Realtime Currency Exchange Rate"] if !ok { return 0 } return int64(fxRate.ExchangeRate * 10000) }