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
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)
|
||
|
|
}
|