From dfae1312e2a8009e20f075c0aa6d26f62617aec8 Mon Sep 17 00:00:00 2001 From: schwarzlichtbezirk Date: Mon, 26 Aug 2024 01:57:37 +0300 Subject: [PATCH] /prop/al/ routes added. --- README.md | 4 +- confdata/slot-club-init.sql | 4 +- go.mod | 4 +- go.sum | 10 ++- spi/errcodes.go | 19 +++++- spi/game.go | 10 +-- spi/logic.go | 8 ++- spi/props.go | 122 +++++++++++++++++++++++++++++++++++- spi/routes.go | 6 +- spi/sqlbatch.go | 33 +++++++++- 10 files changed, 195 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 1cad5e37..dc65323a 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ curl -X GET localhost:8080/gamelist Response has array with available algorithms descriptions. Each structure has a list of games aliases, that shares one algorithm. Field `rtplist` has the list of reels with predefined RTP. There is example of structure with info: ```json -{"aliases":[{"id":"trolls","name":"Trolls"},{"id":"excalibur","name":"Excalibur"},{"id":"pandorasbox","name":"Pandora's Box"},{"id":"wildwitches","name":"Wild Witches"}],"provider":"NetEnt","scrnx":5,"scrny":3,"rtplist":["88","89","92","93","94","95","97","98","102","110"]} +{"aliases":[{"id":"trolls","name":"Trolls"},{"id":"excalibur","name":"Excalibur"},{"id":"pandorasbox","name":"Pandora's Box"},{"id":"wildwitches","name":"Wild Witches"}],"provider":"NetEnt","scrnx":5,"scrny":3,"rtplist":[87.788791,89.230191,93.903358,95.183523,96.6485,98.193276,110.298257,91.925079,93.061471,101.929305]} ``` `/ping`, `/servinfo` and `/memusage`, `/signis`, `/signup` and `/signin` endpoints also does not expects authorization. @@ -88,7 +88,7 @@ There is supported basic authorization and bearer authorization (with JWT-tokens In `/signin` call password can be given by two ways: 1) Explicitly at field `secret` as is. -2) By HMAC SHA256 hash and temporary public key. +2) By HMAC SHA256 hash and temporary public key (without send opened secret). In second case it should be string in field `sigtime` with current time formatted in RFC3339 (can be with nanoseconds). And at field `hs256` it should be hexadecimal HMAC formed with algorithm SHA256 with this current time as a key, and password, i.e. sha256.hmac(sigtime, password). Allowed timeout for public key is 2m 30s. diff --git a/confdata/slot-club-init.sql b/confdata/slot-club-init.sql index 5c0653bc..2f81aa9a 100644 --- a/confdata/slot-club-init.sql +++ b/confdata/slot-club-init.sql @@ -11,8 +11,8 @@ Users access levels (`gal` or `access` fields) are sum of followed ints: 30 - all rights */ -INSERT INTO `club` (`cid`,`ctime`,`utime`,`name`,`bank`,`fund`,`lock`,`jptrate`,`gainrtp`) VALUES -(1,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,'virtual',10000,1000000,0,0.015,95); +INSERT INTO `club` (`cid`,`ctime`,`utime`,`name`,`bank`,`fund`,`lock`,`jptrate`,`mrtp`) VALUES +(1,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,'virtual',10000,1000000,0,0.015,0); INSERT INTO `user` (`uid`,`ctime`,`utime`,`email`,`secret`,`name`,`gal`) VALUES (1,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,'admin@example.org','0YBoaT','admin',30), diff --git a/go.mod b/go.mod index 84ff8803..963256a2 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -51,7 +51,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.9.0 // indirect golang.org/x/crypto v0.26.0 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.17.0 // indirect diff --git a/go.sum b/go.sum index a33381b2..b2de154f 100644 --- a/go.sum +++ b/go.sum @@ -93,8 +93,8 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -122,13 +122,11 @@ github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -145,8 +143,8 @@ golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k= golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= diff --git a/spi/errcodes.go b/spi/errcodes.go index 63796b31..12e9cde3 100644 --- a/spi/errcodes.go +++ b/spi/errcodes.go @@ -158,7 +158,24 @@ const ( SEC_prop_walletadd_noaccess SEC_prop_walletadd_sql - // POST /prop/wallet/get + // POST /prop/al/get + SEC_prop_alget_nobind + SEC_prop_alget_norid + SEC_prop_alget_nouid + SEC_prop_alget_noclub + SEC_prop_alget_nouser + SEC_prop_alget_noaccess + + // POST /prop/al/set + SEC_prop_alset_nobind + SEC_prop_alset_norid + SEC_prop_alset_nouid + SEC_prop_alset_noclub + SEC_prop_alset_nouser + SEC_prop_alset_noaccess + SEC_prop_alset_sql + + // POST /prop/rtp/get SEC_prop_rtpget_nobind SEC_prop_rtpget_norid SEC_prop_rtpget_nouid diff --git a/spi/game.go b/spi/game.go index 731a9b4e..80e19288 100644 --- a/spi/game.go +++ b/spi/game.go @@ -534,14 +534,14 @@ func SpiGameSpin(c *gin.Context) { club.mux.RLock() var bank = club.Bank - var rtp = GetRTP(user, club) + var mrtp = GetRTP(user, club) club.mux.RUnlock() // spin until gain less than bank value var wins game.Wins var n = 0 for { - scene.Game.Spin(scene.Scrn, rtp) + scene.Game.Spin(scene.Scrn, mrtp) scene.Game.Scanner(scene.Scrn, &wins) scene.Game.Spawn(scene.Scrn, wins) banksum = totalbet - wins.Gain() @@ -581,6 +581,7 @@ func SpiGameSpin(c *gin.Context) { var rec = Spinlog{ SID: sid, GID: arg.GID, + MRTP: mrtp, Gain: scene.Game.GetGain(), Wallet: props.Wallet, } @@ -671,13 +672,13 @@ func SpiGameDoubleup(c *gin.Context) { club.mux.RLock() var bank = club.Bank - var rtp = GetRTP(user, club) + var mrtp = GetRTP(user, club) club.mux.RUnlock() var multgain float64 // new multiplied gain if bank >= risk*float64(arg.Mult) { var r = rand.Float64() - var side = 1 / float64(arg.Mult) * rtp / 100 + var side = 1 / float64(arg.Mult) * mrtp / 100 if r < side { multgain = risk * float64(arg.Mult) } @@ -708,6 +709,7 @@ func SpiGameDoubleup(c *gin.Context) { var rec = Multlog{ ID: id, GID: arg.GID, + MRTP: mrtp, Mult: arg.Mult, Risk: risk, Gain: multgain, diff --git a/spi/logic.go b/spi/logic.go index 3b27d042..ebcfdd37 100644 --- a/spi/logic.go +++ b/spi/logic.go @@ -90,6 +90,7 @@ type Spinlog struct { SID uint64 `xorm:"pk" json:"sid" yaml:"sid" xml:"sid,attr"` // spin ID CTime time.Time `xorm:"created 'ctime'" json:"ctime" yaml:"ctime" xml:"ctime"` // creation time GID uint64 `xorm:"notnull" json:"gid" yaml:"gid" xml:"gid,attr"` // game ID + MRTP float64 `xorm:"notnull" json:"mrtp" yaml:"mrtp" xml:"mrtp,attr"` // master RTP Game string `xorm:"notnull" json:"game" yaml:"game" xml:"game"` // game data Screen string `xorm:"notnull" json:"screen,omitempty" yaml:"screen,omitempty" xml:"screen,omitempty"` // game screen marshaled to JSON Wins string `xorm:"text" json:"wins,omitempty" yaml:"wins,omitempty" xml:"wins,omitempty"` // list of wins marshaled to JSON @@ -102,8 +103,9 @@ var SpinCounter uint64 // last spin log ID type Multlog struct { ID uint64 `xorm:"pk" json:"id" yaml:"id" xml:"id,attr"` CTime time.Time `xorm:"created 'ctime'" json:"ctime" yaml:"ctime" xml:"ctime"` - GID uint64 `xorm:"notnull" json:"gid" yaml:"gid" xml:"gid,attr"` // game ID - Mult int `xorm:"notnull" json:"mult" yaml:"mult" xml:"mult"` // multiplier + GID uint64 `xorm:"notnull" json:"gid" yaml:"gid" xml:"gid,attr"` // game ID + MRTP float64 `xorm:"notnull" json:"mrtp" yaml:"mrtp" xml:"mrtp,attr"` // master RTP + Mult int `xorm:"notnull" json:"mult" yaml:"mult" xml:"mult"` // multiplier Risk float64 `xorm:"notnull" json:"risk" yaml:"risk" xml:"risk"` Gain float64 `xorm:"notnull" json:"gain" yaml:"gain" xml:"gain"` Wallet float64 `xorm:"notnull" json:"wallet" yaml:"wallet" xml:"wallet"` @@ -179,7 +181,7 @@ func GetAdmin(c *gin.Context, cid uint64) (*User, AL) { } func GetRTP(user *User, club *Club) float64 { - if props, ok := user.props.Get(club.CID); ok { + if props, ok := user.props.Get(club.CID); ok && props.MRTP != 0 { return props.MRTP } if club.MRTP != 0 { diff --git a/spi/props.go b/spi/props.go index de524aa3..912223e0 100644 --- a/spi/props.go +++ b/spi/props.go @@ -145,6 +145,126 @@ func SpiPropsWalletAdd(c *gin.Context) { RetOk(c, ret) } +// Returns personal access level for pointed user at pointed club. +func SpiPropsAlGet(c *gin.Context) { + var err error + var ok bool + var arg struct { + XMLName xml.Name `json:"-" yaml:"-" xml:"arg"` + CID uint64 `json:"cid" yaml:"cid" xml:"cid,attr" form:"cid"` + UID uint64 `json:"uid" yaml:"uid" xml:"uid,attr" form:"uid"` + } + var ret struct { + XMLName xml.Name `json:"-" yaml:"-" xml:"ret"` + Access AL `json:"access" yaml:"access" xml:"access"` + } + + if err = c.ShouldBind(&arg); err != nil { + Ret400(c, SEC_prop_alget_nobind, err) + return + } + if arg.CID == 0 { + Ret400(c, SEC_prop_alget_norid, ErrNoCID) + return + } + if arg.UID == 0 { + Ret400(c, SEC_prop_alget_nouid, ErrNoUID) + return + } + + var club *Club + if club, ok = Clubs.Get(arg.CID); !ok { + Ret404(c, SEC_prop_alget_noclub, ErrNoClub) + return + } + _ = club + + var user *User + if user, ok = Users.Get(arg.UID); !ok { + Ret404(c, SEC_prop_alget_nouser, ErrNoUser) + return + } + + var admin, al = GetAdmin(c, arg.CID) + if admin != user && al&ALadmin == 0 { + Ret403(c, SEC_prop_alget_noaccess, ErrNoAccess) + return + } + + ret.Access = user.GetAL(arg.CID) + + RetOk(c, ret) +} + +// Set personal access level for given user at given club. +func SpiPropsAlSet(c *gin.Context) { + var err error + var ok bool + var arg struct { + XMLName xml.Name `json:"-" yaml:"-" xml:"arg"` + CID uint64 `json:"cid" yaml:"cid" xml:"cid,attr" form:"cid"` + UID uint64 `json:"uid" yaml:"uid" xml:"uid,attr" form:"uid"` + Access AL `json:"access" yaml:"access" xml:"access"` + } + + if err = c.ShouldBind(&arg); err != nil { + Ret400(c, SEC_prop_alset_nobind, err) + return + } + if arg.CID == 0 { + Ret400(c, SEC_prop_alset_norid, ErrNoCID) + return + } + if arg.UID == 0 { + Ret400(c, SEC_prop_alset_nouid, ErrNoUID) + return + } + + var club *Club + if club, ok = Clubs.Get(arg.CID); !ok { + Ret404(c, SEC_prop_alset_noclub, ErrNoClub) + return + } + _ = club + + var user *User + if user, ok = Users.Get(arg.UID); !ok { + Ret404(c, SEC_prop_alset_nouser, ErrNoUser) + return + } + + var props, hasprops = user.props.Get(arg.CID) + if !hasprops { + props = &Props{ + CID: arg.CID, + UID: arg.UID, + } + } + + var admin, al = GetAdmin(c, arg.CID) + if al&ALadmin == 0 { + Ret403(c, SEC_prop_alset_noaccess, ErrNoAccess) + return + } + _ = admin + + // update access level as transaction + if Cfg.WalletlogBufferSize > 1 { + go BankBat[arg.CID].Access(cfg.XormStorage, arg.UID, arg.Access, !hasprops) + } else if err = BankBat[arg.CID].Access(cfg.XormStorage, arg.UID, arg.Access, !hasprops); err != nil { + Ret500(c, SEC_prop_rtpset_sql, err) + return + } + + // make changes to memory data + props.Access = arg.Access + if !hasprops { + user.InsertProps(props) + } + + c.Status(http.StatusOK) +} + // Returns master RTP for pointed user at pointed club. // This RTP if it set have more priority then club RTP. func SpiPropsRtpGet(c *gin.Context) { @@ -249,7 +369,7 @@ func SpiPropsRtpSet(c *gin.Context) { } _ = admin - // update wallet as transaction + // update master RTP as transaction if Cfg.WalletlogBufferSize > 1 { go BankBat[arg.CID].MRTP(cfg.XormStorage, arg.UID, arg.MRTP, !hasprops) } else if err = BankBat[arg.CID].MRTP(cfg.XormStorage, arg.UID, arg.MRTP, !hasprops); err != nil { diff --git a/spi/routes.go b/spi/routes.go index dc044d4e..255791f9 100644 --- a/spi/routes.go +++ b/spi/routes.go @@ -143,8 +143,10 @@ func Router(r *gin.Engine) { var rp = ra.Group("/prop") rp.POST("/wallet/get", SpiPropsWalletGet) rp.POST("/wallet/add", SpiPropsWalletAdd) - rg.POST("/rtp/get", SpiPropsRtpGet) - rg.POST("/rtp/set", SpiPropsRtpSet) + rp.POST("/al/get", SpiPropsAlGet) + rp.POST("/al/set", SpiPropsAlSet) + rp.POST("/rtp/get", SpiPropsRtpGet) + rp.POST("/rtp/set", SpiPropsRtpSet) var ru = ra.Group("/user") ru.POST("/rename", SpiUserRename) ru.POST("/secret", SpiUserSecret) diff --git a/spi/sqlbatch.go b/spi/sqlbatch.go index 0927c2b7..7cac0ebf 100644 --- a/spi/sqlbatch.go +++ b/spi/sqlbatch.go @@ -10,7 +10,8 @@ import ( const ( sqlbank1 = `UPDATE club SET bank=bank+? WHERE cid=?` sqlbank2 = `UPDATE props SET wallet=wallet+? WHERE uid=? AND cid=?` - sqlbank3 = `UPDATE props SET mrtp=? WHERE uid=? AND cid=?` + sqlbank3 = `UPDATE props SET access=? WHERE uid=? AND cid=?` + sqlbank4 = `UPDATE props SET mrtp=? WHERE uid=? AND cid=?` ) func SafeTransaction(engine *xorm.Engine, f func(*Session) error) (err error) { @@ -37,6 +38,7 @@ type SqlBank struct { cid uint64 banksum float64 usersum map[uint64]float64 + useral map[uint64]AL userrtp map[uint64]float64 userins map[uint64]bool usercap int @@ -52,6 +54,7 @@ func (sb *SqlBank) Init(cid uint64, capacity, logsize int) { sb.cid = cid sb.banksum = 0 sb.usersum = make(map[uint64]float64, capacity) + sb.useral = make(map[uint64]AL, capacity) sb.userrtp = make(map[uint64]float64, capacity) sb.userins = make(map[uint64]bool, capacity) sb.usercap = capacity @@ -62,6 +65,7 @@ func (sb *SqlBank) Init(cid uint64, capacity, logsize int) { func (sb *SqlBank) clear() { sb.banksum = 0 clear(sb.usersum) + clear(sb.useral) clear(sb.userrtp) clear(sb.userins) sb.log = sb.log[:0] @@ -78,11 +82,13 @@ func (sb *SqlBank) transaction(session *Session) (err error) { var pins = make([]Props, 0, len(sb.userins)) for uid := range sb.userins { var sum = sb.usersum[uid] + var al = sb.useral[uid] var mrtp = sb.userrtp[uid] var props = Props{ CID: sb.cid, UID: uid, Wallet: sum, + Access: al, MRTP: mrtp, } pins = append(pins, props) @@ -98,9 +104,16 @@ func (sb *SqlBank) transaction(session *Session) (err error) { } } } + for uid, access := range sb.useral { + if !sb.userins[uid] { + if _, err = session.Exec(sqlbank3, access, uid, sb.cid); err != nil { + return + } + } + } for uid, mrtp := range sb.userrtp { if !sb.userins[uid] { - if _, err = session.Exec(sqlbank3, mrtp, uid, sb.cid); err != nil { + if _, err = session.Exec(sqlbank4, mrtp, uid, sb.cid); err != nil { return } } @@ -150,6 +163,22 @@ func (sb *SqlBank) Add(engine *xorm.Engine, uid, aid uint64, wallet, sum float64 return } +func (sb *SqlBank) Access(engine *xorm.Engine, uid uint64, access AL, ins bool) (err error) { + sb.mux.Lock() + defer sb.mux.Unlock() + sb.useral[uid] = access + if ins { + sb.userins[uid] = ins + } + if len(sb.useral) >= sb.usercap { + if err = SafeTransaction(engine, sb.transaction); err != nil { + return + } + sb.clear() + } + return +} + func (sb *SqlBank) MRTP(engine *xorm.Engine, uid uint64, mrtp float64, ins bool) (err error) { sb.mux.Lock() defer sb.mux.Unlock()