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.
388 lines
8.9 KiB
388 lines
8.9 KiB
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) |
|
}
|
|
|