印度包网
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

389 lines
8.9 KiB

1 year ago
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)
}